25void OverallTests::checkMp3Testfile1()
27 CPPUNIT_ASSERT_EQUAL(ContainerFormat::MpegAudioFrames, m_fileInfo.
containerFormat());
28 const auto tracks = m_fileInfo.
tracks();
29 CPPUNIT_ASSERT_EQUAL(1_st, tracks.size());
30 for (
const auto &track : tracks) {
31 CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
32 CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Mpeg1Audio, track->format().general);
34 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint16_t
>(2), track->channelCount());
35 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint8_t
>(MpegChannelMode::JointStereo), track->channelConfig());
36 CPPUNIT_ASSERT_EQUAL(44100u, track->samplingFrequency());
37 CPPUNIT_ASSERT_EQUAL(3, track->duration().seconds());
39 const auto tags = m_fileInfo.
tags();
40 switch (m_tagStatus) {
42 CPPUNIT_ASSERT(m_fileInfo.
id3v1Tag());
43 CPPUNIT_ASSERT_EQUAL(1_st, m_fileInfo.
id3v2Tags().size());
44 CPPUNIT_ASSERT_EQUAL(2_st, tags.size());
45 for (
const auto &tag : tags) {
46 CPPUNIT_ASSERT_EQUAL(4, tag->value(KnownField::TrackPosition).toPositionInSet().position());
47 CPPUNIT_ASSERT_EQUAL(
"1984"s, tag->value(KnownField::RecordDate).toString());
48 switch (tag->type()) {
49 case TagType::Id3v1Tag:
50 CPPUNIT_ASSERT_EQUAL(
"Cohesion"s, tag->value(KnownField::Title).toString());
51 CPPUNIT_ASSERT_EQUAL(
"Minutemen"s, tag->value(KnownField::Artist).toString());
52 CPPUNIT_ASSERT_EQUAL(
"Double Nickels On The Dime"s, tag->value(KnownField::Album).toString());
53 CPPUNIT_ASSERT_EQUAL(
"Punk Rock"s, tag->value(KnownField::Genre).toString());
54 CPPUNIT_ASSERT_EQUAL(
"ExactAudioCopy v0.95b4"s, tag->value(KnownField::Comment).toString());
56 case TagType::Id3v2Tag:
57 CPPUNIT_ASSERT_EQUAL(TagTextEncoding::Utf16LittleEndian, tag->value(KnownField::Title).dataEncoding());
58 CPPUNIT_ASSERT_EQUAL(u
"Cohesion"s, tag->value(KnownField::Title).toWString());
59 CPPUNIT_ASSERT_EQUAL(
"Cohesion"s, tag->value(KnownField::Title).toString(TagTextEncoding::Utf8));
60 CPPUNIT_ASSERT_EQUAL(u
"Minutemen"s, tag->value(KnownField::Artist).toWString());
61 CPPUNIT_ASSERT_EQUAL(
"Minutemen"s, tag->value(KnownField::Artist).toString(TagTextEncoding::Utf8));
62 CPPUNIT_ASSERT_EQUAL(u
"Double Nickels On The Dime"s, tag->value(KnownField::Album).toWString());
63 CPPUNIT_ASSERT_EQUAL(
"Double Nickels On The Dime"s, tag->value(KnownField::Album).toString(TagTextEncoding::Utf8));
64 CPPUNIT_ASSERT_EQUAL(u
"Punk Rock"s, tag->value(KnownField::Genre).toWString());
65 CPPUNIT_ASSERT_EQUAL(
"Punk Rock"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
66 CPPUNIT_ASSERT_EQUAL(u
"ExactAudioCopy v0.95b4"s, tag->value(KnownField::Comment).toWString());
67 CPPUNIT_ASSERT_EQUAL(
"ExactAudioCopy v0.95b4"s, tag->value(KnownField::Comment).toString(TagTextEncoding::Utf8));
68 CPPUNIT_ASSERT_EQUAL(43, tag->value(KnownField::TrackPosition).toPositionInSet().total());
69 CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
70 CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
77 checkMp3TestMetaData();
80 CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
83 auto warningAboutEncoding =
false;
84 for (
auto &msg : m_diag) {
85 if (msg.message() ==
"The used encoding is unlikely to be supported by other software.") {
86 CPPUNIT_ASSERT_EQUAL(DiagLevel::Warning, msg.level());
87 warningAboutEncoding =
true;
88 msg =
DiagMessage(DiagLevel::Information,
string(),
string());
91 const auto encodingWarningExpected
93 CPPUNIT_ASSERT_EQUAL(encodingWarningExpected, warningAboutEncoding);
94 CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Information);
100void OverallTests::checkMp3Testfile2()
102 CPPUNIT_ASSERT_EQUAL(ContainerFormat::MpegAudioFrames, m_fileInfo.
containerFormat());
103 const auto tracks = m_fileInfo.
tracks();
104 CPPUNIT_ASSERT_EQUAL(1_st, tracks.size());
105 for (
const auto &track : tracks) {
106 CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
107 CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Mpeg1Audio, track->format().general);
109 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint16_t
>(2), track->channelCount());
110 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint8_t
>(MpegChannelMode::Stereo), track->channelConfig());
111 CPPUNIT_ASSERT_EQUAL(44100u, track->samplingFrequency());
112 CPPUNIT_ASSERT_EQUAL(20, track->duration().seconds());
114 const auto tags = m_fileInfo.
tags();
116 switch (m_tagStatus) {
119 CPPUNIT_ASSERT(!m_fileInfo.
id3v1Tag());
120 CPPUNIT_ASSERT_EQUAL(1_st, m_fileInfo.
id3v2Tags().size());
121 CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
122 for (
const auto &tag : tags) {
123 if (tag->type() != TagType::Id3v2Tag) {
124 CPPUNIT_FAIL(argsToString(
"no ", tag->typeName(),
" tag expected"));
126 const auto *
const id3v2Tag =
static_cast<const Id3v2Tag *
>(tag);
129 CPPUNIT_ASSERT_EQUAL(expectId3v24 ? 4 : 3,
static_cast<int>(id3v2Tag->majorVersion()));
130 CPPUNIT_ASSERT_EQUAL(
131 expectId3v24 ? TagTextEncoding::Utf8 : TagTextEncoding::Utf16LittleEndian, tag->value(KnownField::Title).dataEncoding());
132 CPPUNIT_ASSERT_EQUAL(
"Infinite (Original Mix)"s, tag->value(KnownField::Title).toString(TagTextEncoding::Utf8));
133 CPPUNIT_ASSERT_EQUAL(
"B-Front"s, tag->value(KnownField::Artist).toString(TagTextEncoding::Utf8));
134 CPPUNIT_ASSERT_EQUAL(
"Infinite"s, tag->value(KnownField::Album).toString(TagTextEncoding::Utf8));
136 tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
137 CPPUNIT_ASSERT_EQUAL(
"Lavf57.83.100"s, tag->value(KnownField::EncoderSettings).toString(TagTextEncoding::Utf8));
138 CPPUNIT_ASSERT_EQUAL(
"Roughstate"s, tag->value(KnownField::Publisher).toString(TagTextEncoding::Utf8));
139 CPPUNIT_ASSERT_EQUAL(
"2017"s, tag->value(KnownField::RecordDate).toString(TagTextEncoding::Utf8));
140 CPPUNIT_ASSERT_EQUAL(1, tag->value(KnownField::TrackPosition).toPositionInSet().position());
141 CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
142 CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
145 const auto &fields = id3v2Tag->fields();
147 CPPUNIT_ASSERT_MESSAGE(
"genre field present"s, genreFields.first != genreFields.second);
148 const auto &genreField = genreFields.first->second;
149 const auto &additionalValues = genreField.additionalValues();
151 CPPUNIT_ASSERT_EQUAL(
"Test"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
152 CPPUNIT_ASSERT_EQUAL(1_st, additionalValues.size());
153 CPPUNIT_ASSERT_EQUAL(
"Example"s, additionalValues[0].toString(TagTextEncoding::Utf8));
155 CPPUNIT_ASSERT_EQUAL(
"Hardstyle"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
156 CPPUNIT_ASSERT_EQUAL(3_st, additionalValues.size());
157 CPPUNIT_ASSERT_EQUAL(
"Test"s, additionalValues[0].toString(TagTextEncoding::Utf8));
158 CPPUNIT_ASSERT_EQUAL(
"Example"s, additionalValues[1].toString(TagTextEncoding::Utf8));
159 CPPUNIT_ASSERT_EQUAL(
"Hard Dance"s, additionalValues[2].toString(TagTextEncoding::Utf8));
161 CPPUNIT_ASSERT_MESSAGE(
"exactly one genre field present"s, ++genreFields.first == genreFields.second);
164 const auto artists = id3v2Tag->values(KnownField::Artist);
166 CPPUNIT_ASSERT_EQUAL(
"B-Front"s, artists[0]->toString(TagTextEncoding::Utf8));
167 CPPUNIT_ASSERT_EQUAL(
"Second Artist Example"s, artists[1]->toString(TagTextEncoding::Utf8));
169 CPPUNIT_ASSERT_EQUAL(
"3rd Artist Example"s, artists[2]->toString(TagTextEncoding::Utf8));
172 const auto genres = id3v2Tag->values(KnownField::Genre);
174 CPPUNIT_ASSERT_EQUAL(2_st, genres.size());
175 CPPUNIT_ASSERT_EQUAL(
"Test"s, genres[0]->toString(TagTextEncoding::Utf8));
176 CPPUNIT_ASSERT_EQUAL(
"Example"s, genres[1]->toString(TagTextEncoding::Utf8));
178 CPPUNIT_ASSERT_EQUAL(4_st, genres.size());
179 CPPUNIT_ASSERT_EQUAL(
"Hardstyle"s, genres[0]->toString(TagTextEncoding::Utf8));
180 CPPUNIT_ASSERT_EQUAL(
"Test"s, genres[1]->toString(TagTextEncoding::Utf8));
181 CPPUNIT_ASSERT_EQUAL(
"Example"s, genres[2]->toString(TagTextEncoding::Utf8));
182 CPPUNIT_ASSERT_EQUAL(
"Hard Dance"s, genres[3]->toString(TagTextEncoding::Utf8));
187 CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
191 CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Information);
195 CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Warning);
196 int warningCount = 0;
197 for (
const auto &msg : m_diag) {
198 if (msg.level() != DiagLevel::Warning) {
202 TESTUTILS_ASSERT_LIKE(
"context",
"(parsing|making) (TPE1|TCON)( frame)?", msg.context());
203 TESTUTILS_ASSERT_LIKE(
"message",
204 "Multiple strings (found|assigned) .*"
205 "Additional (values \"Second Artist Example\" and \"3rd Artist Example\" are|"
206 "value \"Example\" is) "
207 "supposed to be ignored.",
210 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"exactly 4 warnings present", 4, warningCount);
216void OverallTests::checkMp3TestMetaData()
223 if (m_mode & Id3v2AndId3v1) {
224 CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.
id3v1Tag());
225 CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.
id3v2Tags().at(0).get());
226 }
else if (m_mode & Id3v1Only) {
227 CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.
id3v1Tag());
228 CPPUNIT_ASSERT(m_fileInfo.
id3v2Tags().empty());
230 CPPUNIT_ASSERT(!m_fileInfo.
id3v1Tag());
231 CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.
id3v2Tags().at(0).get());
236 CPPUNIT_ASSERT_EQUAL(TagTextEncoding::Latin1, id3v1Tag->
value(KnownField::Title).
dataEncoding());
237 CPPUNIT_ASSERT_EQUAL(m_testTitle, id3v1Tag->
value(KnownField::Title));
238 CPPUNIT_ASSERT_EQUAL(m_testCommentWithoutDescription, id3v1Tag->
value(KnownField::Comment));
239 CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v1Tag->
value(KnownField::Album));
240 CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v1Tag->
value(KnownField::Artist));
241 m_preservedMetaData.pop();
244 const TagValue &titleValue = id3v2Tag->
value(KnownField::Title);
245 const TagValue &commentValue = id3v2Tag->
value(KnownField::Comment);
247 if (m_mode & UseId3v24) {
248 CPPUNIT_ASSERT_EQUAL(TagTextEncoding::Utf8, titleValue.
dataEncoding());
249 CPPUNIT_ASSERT_EQUAL(m_testTitle, titleValue);
250 CPPUNIT_ASSERT_EQUAL(m_testComment, commentValue);
251 CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v2Tag->
value(KnownField::Album));
252 CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->
value(KnownField::Artist));
255 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"not attempted to use UTF-8 in ID3v2.3", TagTextEncoding::Utf16LittleEndian, titleValue.
dataEncoding());
256 CPPUNIT_ASSERT_EQUAL(m_testTitle, titleValue);
257 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"not attempted to use UTF-8 in ID3v2.3", TagTextEncoding::Utf16LittleEndian, commentValue.
dataEncoding());
258 CPPUNIT_ASSERT_EQUAL_MESSAGE(
259 "not attempted to use UTF-8 in ID3v2.3", TagTextEncoding::Utf16LittleEndian, commentValue.
descriptionEncoding());
260 CPPUNIT_ASSERT_EQUAL(m_testComment, commentValue);
261 CPPUNIT_ASSERT_EQUAL_MESSAGE(
262 "description is also converted to UTF-16",
"s\0o\0m\0e\0 \0d\0e\0s\0c\0r\0i\0p\0t\0i\0\xf3\0n\0"s, commentValue.
description());
263 CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v2Tag->
value(KnownField::Album));
264 CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->
value(KnownField::Artist));
268 m_preservedMetaData.pop();
277 CPPUNIT_ASSERT_EQUAL(m_testPosition, id3v2Tag->
value(KnownField::TrackPosition));
278 CPPUNIT_ASSERT_EQUAL(m_testPosition, id3v2Tag->
value(KnownField::DiskPosition));
285void OverallTests::checkMp3PaddingConstraints()
289 if (!(m_mode & Id3v1Only)) {
290 if (m_mode & PaddingConstraints) {
291 if (m_mode & ForceRewring) {
292 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint64_t
>(4096), m_fileInfo.
paddingSize());
295 CPPUNIT_ASSERT(m_fileInfo.
paddingSize() <= (4096 + 1024));
307void OverallTests::setMp3TestMetaData1()
314 if (m_mode & Id3v2AndId3v1) {
317 }
else if (m_mode & Id3v1Only) {
324 if (!(m_mode & Id3v1Only) && m_mode & UseId3v24) {
329 for (
Tag *
const tag : initializer_list<Tag *>{ id3v1Tag, id3v2Tag }) {
333 tag->setValue(KnownField::Title, m_testTitle);
334 tag->setValue(KnownField::Comment, m_testComment);
335 tag->setValue(KnownField::Album, m_testAlbum);
336 m_preservedMetaData.push(tag->value(KnownField::Artist));
337 tag->setValue(KnownField::TrackPosition, m_testPosition);
338 tag->setValue(KnownField::DiskPosition, m_testPosition);
349void OverallTests::setMp3TestMetaData2()
353 CPPUNIT_ASSERT_EQUAL(1_st, m_fileInfo.
id3v2Tags().size());
354 auto &id3v2Tag(m_fileInfo.
id3v2Tags().front());
355 id3v2Tag->
setVersion((m_mode & UseId3v24) ? 4 : 3, 0);
356 const auto artists = id3v2Tag->
values(KnownField::Artist);
357 CPPUNIT_ASSERT_EQUAL(2_st, artists.size());
358 id3v2Tag->
setValues(KnownField::Artist, { *artists[0], *artists[1],
TagValue(
"3rd Artist Example") });
367 cerr << endl <<
"MP3 parser" << endl;
370 parseFile(testFilePath(
"mtx-test-data/mp3/id3-tag-and-xing-header.mp3"), &OverallTests::checkMp3Testfile1);
371 parseFile(testFilePath(
"misc/multiple_id3v2_4_values.mp3"), &OverallTests::checkMp3Testfile2);
384 for (m_mode = 0; m_mode != 0x20; ++m_mode) {
389 if (m_mode & UseId3v24) {
390 if (m_mode & Id3v1Only) {
397 m_fileInfo.
setMinPadding(m_mode & PaddingConstraints ? 1024 : 0);
398 m_fileInfo.
setMaxPadding(m_mode & PaddingConstraints ? (4096 + 1024) : numeric_limits<size_t>::max());
403 list<string> testConditions;
404 if (m_mode & ForceRewring) {
405 testConditions.emplace_back(
"forcing rewrite");
407 if (m_mode & Id3v2AndId3v1) {
408 if (m_mode & RemoveTag) {
409 testConditions.emplace_back(
"removing tag");
411 testConditions.emplace_back(
"ID3v1 and ID3v2");
413 }
else if (m_mode & Id3v1Only) {
414 testConditions.emplace_back(
"ID3v1 only");
416 testConditions.emplace_back(
"ID3v2 only");
418 if (m_mode & PaddingConstraints) {
419 testConditions.emplace_back(
"padding constraints");
421 if (m_mode & UseId3v24) {
422 testConditions.emplace_back(
"use ID3v2.4");
424 cerr << endl <<
"MP3 maker - testmode " << m_mode <<
": " << joinStrings(testConditions,
", ") << endl;
428 makeFile(workingCopyPath(
"mtx-test-data/mp3/id3-tag-and-xing-header.mp3"),
429 (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setMp3TestMetaData1, &OverallTests::checkMp3Testfile1);
430 makeFile(workingCopyPath(
"misc/multiple_id3v2_4_values.mp3"),
431 (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setMp3TestMetaData2, &OverallTests::checkMp3Testfile2);
void testMp3Parsing()
Tests the MP3 parser via MediaFileInfo.
void testMp3Making()
Tests the MP3 maker via MediaFileInfo.
The DiagMessage class holds an information, warning or error gathered during parsing or making.
const TagValue & value(const IdentifierType &id) const
Returns the value of the field with the specified id.
std::vector< const TagValue * > values(const IdentifierType &id) const
Returns the values of the field with the specified id.
bool setValues(const IdentifierType &id, const std::vector< TagValue > &values)
Assigns the given values to the field with the specified id.
Implementation of TagParser::Tag for ID3v1 tags.
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
const TagValue & value(KnownField value) const override
Returns the value of the specified field.
Implementation of TagParser::Tag for ID3v2 tags.
void setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion.
constexpr std::int32_t position() const
Returns the element position of the current instance.
The TagValue class wraps values of different types.
TagTextEncoding dataEncoding() const
Returns the data encoding.
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
const std::string & description() const
Returns the description.
The Tag class is used to store, read and write tag information.