Tag Parser 12.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
overallogg.cpp
Go to the documentation of this file.
1#include "./helper.h"
2#include "./overall.h"
3
4#include "../abstracttrack.h"
5#include "../tag.h"
9
10#include <c++utilities/io/misc.h>
11
12#include <algorithm>
13
14using namespace CppUtilities;
15
19void OverallTests::checkOggTestfile1()
20{
21 CPPUNIT_ASSERT_EQUAL(ContainerFormat::Ogg, m_fileInfo.containerFormat());
22 const auto tracks = m_fileInfo.tracks();
23 CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
24 for (const auto &track : tracks) {
25 switch (track->id()) {
26 case 897658443:
27 CPPUNIT_ASSERT_EQUAL(MediaType::Video, track->mediaType());
28 CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Theora, track->format().general);
29 break;
30 case 1755441791:
31 CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
32 CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Vorbis, track->format().general);
33 CPPUNIT_ASSERT_EQUAL(static_cast<std::uint16_t>(2), track->channelCount());
34 CPPUNIT_ASSERT_EQUAL(44100u, track->samplingFrequency());
35 CPPUNIT_ASSERT_EQUAL(4, track->duration().minutes());
36 break;
37 default:
38 CPPUNIT_FAIL("unknown track ID");
39 }
40 }
41 const auto tags = m_fileInfo.tags();
42 switch (m_tagStatus) {
44 CPPUNIT_ASSERT(m_fileInfo.hasAnyTag());
45 CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
46 CPPUNIT_ASSERT_EQUAL("ffmpeg2theora 0.13"s, tags.front()->value(KnownField::Encoder).toString());
47 CPPUNIT_ASSERT_EQUAL(std::vector<std::uint64_t>{ 0x68a1ea7f }, tags.front()->target().tracks());
48 // Theora tags are currently not supported and hence only the Vorbis comment is
49 // taken into account here
50 break;
52 checkOggTestMetaData();
53 break;
55 CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
56 }
57
58 CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Information);
59}
60
64void OverallTests::checkOggTestfile2()
65{
66 CPPUNIT_ASSERT_EQUAL(ContainerFormat::Ogg, m_fileInfo.containerFormat());
67 const auto tracks = m_fileInfo.tracks();
68 CPPUNIT_ASSERT_EQUAL(1_st, tracks.size());
69 for (const auto &track : tracks) {
70 switch (track->id()) {
71 case 1375632254:
72 CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
73 CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Opus, track->format().general);
74 CPPUNIT_ASSERT_EQUAL(static_cast<std::uint16_t>(2), track->channelCount());
75 CPPUNIT_ASSERT_EQUAL(48000u, track->samplingFrequency());
76 CPPUNIT_ASSERT_EQUAL(1, track->duration().minutes());
77 break;
78 default:
79 CPPUNIT_FAIL("unknown track ID");
80 }
81 }
82 const auto tags = m_fileInfo.tags();
83 switch (m_tagStatus) {
85 CPPUNIT_ASSERT(m_fileInfo.hasAnyTag());
86 CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
87 CPPUNIT_ASSERT_EQUAL("opusenc from opus-tools 0.1.6"s, tags.front()->value(KnownField::Encoder).toString());
88 break;
90 checkOggTestMetaData();
91 break;
93 CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
94 }
95
96 CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Information);
97}
98
102void OverallTests::checkOggTestfile3()
103{
104 CPPUNIT_ASSERT_EQUAL(ContainerFormat::Ogg, m_fileInfo.containerFormat());
105 const auto tracks = m_fileInfo.tracks();
106 CPPUNIT_ASSERT_EQUAL(1_st, tracks.size());
107 for (const auto &track : tracks) {
108 switch (track->id()) {
109 case 1843569915:
110 CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
111 CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Opus, track->format().general);
112 CPPUNIT_ASSERT_EQUAL(static_cast<std::uint16_t>(2), track->channelCount());
113 CPPUNIT_ASSERT_EQUAL(48000u, track->samplingFrequency());
114 CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(19.461), track->duration());
115 break;
116 default:
117 CPPUNIT_FAIL("unknown track ID");
118 }
119 }
120 const auto tags = m_fileInfo.tags();
121 switch (m_tagStatus) {
123 CPPUNIT_ASSERT(m_fileInfo.hasAnyTag());
124 CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
125 CPPUNIT_ASSERT_EQUAL("Lavf58.76.100"s, tags.front()->value(KnownField::Encoder).toString());
126 CPPUNIT_ASSERT_EQUAL("eng"s, tags.front()->value(KnownField::Language).toString());
127 [[fallthrough]];
129 checkOggTestMetaDataCover();
130 break;
132 CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
133 }
134
135 if (m_tagStatus != TagStatus::Original) {
136 CPPUNIT_ASSERT_MESSAGE("no warnings for non-broken file", m_diag.level() <= DiagLevel::Information);
137 return;
138 }
139 CPPUNIT_ASSERT_EQUAL_MESSAGE("warning present", DiagLevel::Warning, m_diag.level());
140 CPPUNIT_ASSERT(std::find_if(m_diag.begin(), m_diag.end(), [](const auto &msg) {
141 return startsWith(msg.message(), "3 bytes left in last segment");
142 }) != m_diag.end());
143 CPPUNIT_ASSERT(std::find_if(m_diag.begin(), m_diag.end(), [](const auto &msg) {
144 return startsWith(msg.message(), "Tag spans over 6 pages but absolute granule position of unfinished page at");
145 }) != m_diag.end());
146 CPPUNIT_ASSERT(std::find_if(m_diag.begin(), m_diag.end(), [](const auto &msg) {
147 return startsWith(msg.message(), "The tag is continued in Ogg page at");
148 }) != m_diag.end());
149}
150
154void OverallTests::checkOggTestMetaData()
155{
156 // check whether a tag is assigned
157 const auto tags = m_fileInfo.tags();
158 const auto *const tag = m_fileInfo.vorbisComment();
159 CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
160 CPPUNIT_ASSERT(tag != nullptr);
161
162 // check test meta data
163 CPPUNIT_ASSERT_EQUAL(m_testTitle, tag->value(KnownField::Title));
164 CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), tag->value(KnownField::Comment).toString()); // loss of description is ok
165 CPPUNIT_ASSERT_EQUAL(m_testAlbum, tag->value(KnownField::Album));
166 CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), tag->value(KnownField::Artist));
167 CPPUNIT_ASSERT_EQUAL(m_testPosition, tag->value(KnownField::TrackPosition));
168 CPPUNIT_ASSERT_EQUAL(m_testPosition, tag->value(KnownField::DiskPosition));
169 // TODO: check more fields
170 m_preservedMetaData.pop();
171}
172
173void OverallTests::checkOggTestMetaDataCover()
174{
175 // check whether a tag is assigned
176 const auto tags = m_fileInfo.tags();
177 const auto *const tag = m_fileInfo.vorbisComment();
178 CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
179 CPPUNIT_ASSERT(tag != nullptr);
180
181 const auto expectedCoverData = readFile(testFilePath("ogg/example-cover.png"));
182 CPPUNIT_ASSERT_EQUAL_MESSAGE("expected cover assigned", std::string_view(expectedCoverData), tag->value(KnownField::Cover).data());
183}
184
185void OverallTests::setOggTestMetaData()
186{
187 // ensure a tag exists
188 auto *const tag = m_fileInfo.createVorbisComment();
189
190 // assign test meta data
191 tag->setValue(KnownField::Title, m_testTitle);
192 tag->setValue(KnownField::Comment, m_testComment);
193 tag->setValue(KnownField::Album, m_testAlbum);
194 m_preservedMetaData.push(tag->value(KnownField::Artist));
195 tag->setValue(KnownField::TrackPosition, m_testPosition);
196 tag->setValue(KnownField::DiskPosition, m_testPosition);
197 // TODO: set more fields
198}
199
200void OverallTests::setOggTestMetaDataCover()
201{
202 auto *const tag = m_fileInfo.createVorbisComment();
203 const auto cover = readFile(testFilePath("ogg/example-cover.png"));
204 tag->setValue(KnownField::Cover, TagValue(cover.data(), cover.size(), TagDataType::Picture));
205}
206
212{
213 cerr << endl << "Ogg parser" << endl;
214 m_fileInfo.setForceFullParse(false);
215 m_tagStatus = TagStatus::Original;
216 parseFile(testFilePath("mtx-test-data/ogg/qt4dance_medium.ogg"), &OverallTests::checkOggTestfile1);
217 parseFile(testFilePath("mtx-test-data/opus/v-opus.ogg"), &OverallTests::checkOggTestfile2);
218 parseFile(testFilePath("ogg/noise-broken-segment-termination.opus"), &OverallTests::checkOggTestfile3);
219}
220
228{
229 // full parse is required to determine padding
230 m_fileInfo.setForceFullParse(true);
231
232 // do the test under different conditions
233 for (m_mode = 0; m_mode != 0x2; ++m_mode) {
234 using namespace SimpleTestFlags;
235
236 // no need to setup test conditions because the Ogg maker
237 // doesn't take those settings into account (currently)
238
239 // print test conditions
240 list<string> testConditions;
241 if (m_mode & RemoveTag) {
242 testConditions.emplace_back("removing tag");
243 } else {
244 testConditions.emplace_back("modifying tag");
245 }
246 cerr << endl << "Ogg maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
247
248 // do actual tests
249 m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
250 const auto modifyRoutine = (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setOggTestMetaData;
251 const auto modifyRoutineCover = (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setOggTestMetaDataCover;
252 makeFile(workingCopyPath("mtx-test-data/ogg/qt4dance_medium.ogg"), modifyRoutine, &OverallTests::checkOggTestfile1);
253 makeFile(workingCopyPath("mtx-test-data/opus/v-opus.ogg"), modifyRoutine, &OverallTests::checkOggTestfile2);
254 makeFile(workingCopyPath("ogg/noise-without-cover.opus"), modifyRoutineCover, &OverallTests::checkOggTestfile3);
255 }
256}
257
262{
263 const auto context = std::string();
264 const auto trackNumberFieldId = std::string(VorbisCommentIds::trackNumber());
265 const auto trackTotalFieldId = std::string(VorbisCommentIds::trackTotal());
266 const auto diskNumberFieldId = std::string(VorbisCommentIds::diskNumber());
267 const auto diskTotalFieldId = std::string(VorbisCommentIds::diskTotal());
268
269 auto diag = Diagnostics();
270 auto vc = VorbisComment();
271 auto trackNumber = VorbisCommentField(trackNumberFieldId, TagValue(5));
272 auto trackTotal = VorbisCommentField(trackTotalFieldId, TagValue(20));
273 auto &fields = vc.fields();
274 fields.insert(std::make_pair(trackNumberFieldId, std::move(trackNumber)));
275 fields.insert(std::make_pair(trackTotalFieldId, std::move(trackTotal)));
276 vc.convertTotalFields(context, diag);
277
278 const auto convertedValues = vc.values(trackNumberFieldId);
279 CPPUNIT_ASSERT_EQUAL_MESSAGE("the two fileds have been combined into one", 1_st, fields.size());
280 CPPUNIT_ASSERT_EQUAL_MESSAGE("there is exactly one track number value", 1_st, convertedValues.size());
281 const auto convertedTrackNumber = convertedValues.front()->toPositionInSet();
282 CPPUNIT_ASSERT_EQUAL(PositionInSet(5, 20), convertedTrackNumber);
283 CPPUNIT_ASSERT_EQUAL(0_st, diag.size());
284
285 auto diskNumber = VorbisCommentField(diskNumberFieldId, TagValue("invalid pos"));
286 auto diskTotal = VorbisCommentField(diskTotalFieldId, TagValue("invalid total"));
287 auto diskTotal2 = VorbisCommentField(diskTotalFieldId, TagValue(42));
288 fields.insert(std::make_pair(diskNumberFieldId, std::move(diskNumber)));
289 fields.insert(std::make_pair(diskTotalFieldId, std::move(diskTotal)));
290 fields.insert(std::make_pair(diskTotalFieldId, std::move(diskTotal2)));
291 vc.convertTotalFields(context, diag);
292
293 const auto newDiskNumberValues = vc.values(diskNumberFieldId);
294 const auto newDiskTotalValues = vc.values(diskTotalFieldId);
295 CPPUNIT_ASSERT_EQUAL_MESSAGE("invalid fields have not been combined", 4_st, fields.size());
296 CPPUNIT_ASSERT_EQUAL_MESSAGE("invalid disk position has been preserved and valid disk total converted", 2_st, newDiskNumberValues.size());
297 CPPUNIT_ASSERT_EQUAL_MESSAGE("invalid disk total has been preserved", 1_st, newDiskTotalValues.size());
298 const auto preservedDiskNumber = newDiskNumberValues[0]->toString();
299 const auto convertedDiskTotal = newDiskNumberValues[1]->toPositionInSet();
300 const auto preservedDiskTotal = newDiskTotalValues[0]->toString();
301 CPPUNIT_ASSERT_EQUAL("invalid pos"s, preservedDiskNumber);
302 CPPUNIT_ASSERT_EQUAL(PositionInSet(0, 42), convertedDiskTotal);
303 CPPUNIT_ASSERT_EQUAL("invalid total"s, preservedDiskTotal);
304 CPPUNIT_ASSERT_EQUAL(3_st, diag.size());
305}
void testOggParsing()
Tests the Ogg parser via MediaFileInfo.
void testVorbisCommentFieldHandling()
Tests the Vorbis Comment specifc handling of certain fields done in VorbisComment::convertTotalFields...
void testOggMaking()
Tests the Ogg maker via MediaFileInfo.
The Diagnostics class is a container for DiagMessage.
DiagLevel level() const
Returns the worst diag level present in the container.
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
VorbisComment * createVorbisComment()
Creates a Vorbis comment for the current file.
VorbisComment * vorbisComment() const
Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
void setForceFullParse(bool forceFullParse)
Sets whether forcing a full parse is enabled.
void tags(std::vector< Tag * > &tags) const
Stores all tags assigned to the current file in the specified vector.
ContainerFormat containerFormat() const
Returns the container format of the current file.
bool hasAnyTag() const
Returns an indication whether a tag of any format is assigned.
The TagValue class wraps values of different types.
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition tagvalue.h:450
The VorbisCommentField class is used by VorbisComment to store the fields.
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
constexpr TAG_PARSER_EXPORT std::string_view trackNumber()
constexpr TAG_PARSER_EXPORT std::string_view diskTotal()
constexpr TAG_PARSER_EXPORT std::string_view trackTotal()
constexpr TAG_PARSER_EXPORT std::string_view diskNumber()
constexpr TAG_PARSER_EXPORT std::string_view cover()
@ TestMetaDataPresent