Tag Parser  7.0.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
utils.cpp
Go to the documentation of this file.
1 #include "./helper.h"
2 
3 #include "../aspectratio.h"
4 #include "../backuphelper.h"
5 #include "../exceptions.h"
6 #include "../margin.h"
7 #include "../mediafileinfo.h"
8 #include "../mediaformat.h"
9 #include "../signature.h"
10 #include "../size.h"
11 #include "../tagtarget.h"
12 
13 #include <c++utilities/conversion/stringbuilder.h>
14 #include <c++utilities/io/catchiofailure.h>
15 #include <c++utilities/tests/testutils.h>
16 using namespace TestUtilities;
17 
18 #include <cppunit/TestFixture.h>
19 #include <cppunit/extensions/HelperMacros.h>
20 
21 #include <cstdio>
22 
23 using namespace std;
24 using namespace TagParser;
25 using namespace ConversionUtilities;
26 using namespace IoUtilities;
27 using namespace TestUtilities::Literals;
28 
29 using namespace CPPUNIT_NS;
30 
34 class UtilitiesTests : public TestFixture {
35  CPPUNIT_TEST_SUITE(UtilitiesTests);
36  CPPUNIT_TEST(testSize);
37  CPPUNIT_TEST(testTagTarget);
38  CPPUNIT_TEST(testSignature);
39  CPPUNIT_TEST(testMargin);
40  CPPUNIT_TEST(testAspectRatio);
41  CPPUNIT_TEST(testMediaFormat);
42 #ifdef PLATFORM_UNIX
43  CPPUNIT_TEST(testBackupFile);
44 #endif
45  CPPUNIT_TEST_SUITE_END();
46 
47 public:
48  void setUp();
49  void tearDown();
50 
51  void testSize();
52  void testStatusProvider();
53  void testTagTarget();
54  void testSignature();
55  void testMargin();
56  void testAspectRatio();
57  void testMediaFormat();
58 #ifdef PLATFORM_UNIX
59  void testBackupFile();
60 #endif
61 };
62 
64 
66 {
67 }
68 
70 {
71 }
72 
74 {
75  static_assert(Size().isNull(), "Size::isNull()");
76  static_assert(!Size(3, 4).isNull(), "Size::isNull()");
77  static_assert(Size(3, 4).resolution() == 12, "Size::resolution");
78 
79  Size size(1920, 1080);
80  CPPUNIT_ASSERT_EQUAL("width: 1920, height: 1080"s, size.toString());
81  CPPUNIT_ASSERT_EQUAL("1080p"s, string(size.abbreviation()));
82  size.setWidth(1280);
83  size.setHeight(720);
84  CPPUNIT_ASSERT_EQUAL("720p"s, string(size.abbreviation()));
85 }
86 
88 {
89  TagTarget target;
90  CPPUNIT_ASSERT(target.isEmpty());
91  CPPUNIT_ASSERT_EQUAL_MESSAGE("default level is 50", 50ul, target.level());
92  CPPUNIT_ASSERT_EQUAL("level 50"s, target.toString(TagTargetLevel::Unspecified));
93  target = TagTarget(30, { 1, 2, 3 }, { 4 }, { 5, 6 }, { 7, 8, 9 });
94  CPPUNIT_ASSERT(!target.isEmpty());
95  const auto mapping = [](uint64 level) { return level == 30 ? TagTargetLevel::Track : TagTargetLevel::Unspecified; };
96  CPPUNIT_ASSERT_EQUAL(
97  "level 30 'track, song, chapter', track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s,
98  target.toString(mapping));
99  target.setLevel(40);
100  CPPUNIT_ASSERT_EQUAL("level 40, track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s,
101  target.toString(mapping));
102  target.setLevelName("test");
103  CPPUNIT_ASSERT_EQUAL("level 40 'test', track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s,
104  target.toString(mapping));
105  CPPUNIT_ASSERT(target == TagTarget(40, { 1, 2, 3 }, { 4 }, { 5, 6 }, { 7, 8, 9 }));
106  target.clear();
107  CPPUNIT_ASSERT(target.isEmpty());
108 }
109 
111 {
112  const unsigned char xzHead[12] = { 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04, 0xe6, 0xd6, 0xb4, 0x46 };
113 
114  // truncated buffer
115  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast<const char *>(xzHead), 3));
116  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast<const char *>(xzHead), 2));
117  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast<const char *>(xzHead), 0));
118 
119  const auto containerFormat = parseSignature(reinterpret_cast<const char *>(xzHead), sizeof(xzHead));
120  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Xz, containerFormat);
121  CPPUNIT_ASSERT_EQUAL("xz compressed file"s, string(containerFormatName(containerFormat)));
122  CPPUNIT_ASSERT_EQUAL("xz"s, string(containerFormatAbbreviation(containerFormat)));
123  CPPUNIT_ASSERT_EQUAL(string(), string(containerFormatSubversion(containerFormat)));
124 }
125 
127 {
128  static_assert(Margin().isNull(), "empty margin");
129  static_assert(!Margin(0, 2).isNull(), "non-empty margin");
130 
131  CPPUNIT_ASSERT_EQUAL("top: 1; left: 2; bottom: 3; right: 4"s, Margin(1, 2, 3, 4).toString());
132 }
133 
135 {
136  static_assert(!AspectRatio().isValid(), "invalid aspect ratio");
137  static_assert(AspectRatio(16, 9).isValid(), "valid aspect ratio");
138  static_assert(AspectRatio(16, 9).isExtended(), "extended aspect ratio");
139 
140  const AspectRatio ratio(4);
141  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(16), ratio.numerator);
142  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(11), ratio.denominator);
143  const AspectRatio ratio2(77);
144  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(0), ratio2.numerator);
145  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(0), ratio2.denominator);
146 }
147 
149 {
150  // unspecific format
152  CPPUNIT_ASSERT_EQUAL("Advanced Audio Coding"s, string(aac.name()));
153  CPPUNIT_ASSERT_EQUAL("AAC"s, string(aac.abbreviation()));
154  CPPUNIT_ASSERT_EQUAL("AAC"s, string(aac.shortAbbreviation()));
155 
156  // specific format
158  CPPUNIT_ASSERT(aac == GeneralMediaFormat::Aac);
159  CPPUNIT_ASSERT(aac != GeneralMediaFormat::Mpeg1Audio);
160  CPPUNIT_ASSERT_EQUAL("Advanced Audio Coding Low Complexity Profile"s, string(aac.name()));
161  CPPUNIT_ASSERT_EQUAL("MPEG-4 AAC-LC"s, string(aac.abbreviation()));
162  CPPUNIT_ASSERT_EQUAL("HE-AAC"s, string(aac.shortAbbreviation()));
163  CPPUNIT_ASSERT_EQUAL("Spectral Band Replication / HE-AAC"s, string(aac.extensionName()));
164 }
165 
166 #ifdef PLATFORM_UNIX
167 void UtilitiesTests::testBackupFile()
168 {
169  using namespace BackupHelper;
170 
171  // ensure backup directory is empty, so backups will be created in the same directory
172  // as the original file
173  backupDirectory().clear();
174 
175  // setup testfile
176  MediaFileInfo file(workingCopyPath("unsupported.bin"));
177  const auto workingDir(file.containingDirectory());
178  file.open();
179 
180  // create backup file
181  string backupPath1, backupPath2;
182  NativeFileStream backupStream1, backupStream2;
183  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
184  CPPUNIT_ASSERT_EQUAL(workingDir + "/unsupported.bin.bak", backupPath1);
185 
186  // recreate original file (like the 'make' methods would do to apply changes)
187  file.stream().open(file.path(), ios_base::out);
188  file.stream() << "test1" << endl;
189 
190  // create a 2nd backup which should not override the first one
191  createBackupFile(file.path(), backupPath2, file.stream(), backupStream2);
192  CPPUNIT_ASSERT_EQUAL(workingDir + "/unsupported.bin.1.bak", backupPath2);
193 
194  // get rid of 2nd backup, recreate original file
195  backupStream2.close();
196  remove(backupPath2.data());
197  file.stream().open(file.path(), ios_base::out);
198  file.stream() << "test2" << endl;
199 
200  // create backup under another location
201  backupDirectory() = "bak";
202  try {
203  createBackupFile(file.path(), backupPath2, file.stream(), backupStream2);
204  CPPUNIT_FAIL("renaming failed because backup dir does not exist");
205  } catch (...) {
206  const char *what = catchIoFailure();
207  CPPUNIT_ASSERT(strstr(what, "Unable to rename original file before rewriting it."));
208  }
209  backupStream2.clear();
210  workingCopyPathMode("bak/unsupported.bin", WorkingCopyMode::NoCopy);
211  createBackupFile(file.path(), backupPath2, file.stream(), backupStream2);
212  CPPUNIT_ASSERT_EQUAL(workingDir + "/bak/unsupported.bin", backupPath2);
213 
214  // get rid of 2nd backup (again)
215  backupStream2.close();
216  CPPUNIT_ASSERT_EQUAL(0, remove(backupPath2.data()));
217  CPPUNIT_ASSERT_EQUAL(0, remove(argsToString(workingDir % '/' + backupDirectory()).data()));
218 
219  // should be able to use backup stream, eg. seek to the end
220  backupStream1.seekg(0, ios_base::end);
221  CPPUNIT_ASSERT_EQUAL(41_st, static_cast<size_t>(backupStream1.tellg()));
222 
223  // restore backup
224  restoreOriginalFileFromBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
225 
226  // check restored backup
227  file.open(true);
228  file.stream().seekg(0x1D);
229  CPPUNIT_ASSERT_EQUAL(0x34_st, static_cast<size_t>(file.stream().get()));
230  file.close();
231 
232  // reset backup dir again
233  backupDirectory().clear();
234 
235  // restore after user aborted
236  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
237  try {
239  } catch (...) {
240  Diagnostics diag;
241  CPPUNIT_ASSERT_THROW(
242  handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, diag, "test"), OperationAbortedException);
243  CPPUNIT_ASSERT(diag.level() < DiagLevel::Critical);
244  CPPUNIT_ASSERT(!diag.empty());
245  CPPUNIT_ASSERT_EQUAL("Rewriting the file to apply changed tag information has been aborted."s, diag.front().message());
246  CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, diag.back().message());
247  }
248 
249  // restore after error
250  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
251  try {
252  throw Failure();
253  } catch (...) {
254  Diagnostics diag;
255  CPPUNIT_ASSERT_THROW(handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, diag, "test"), Failure);
256  CPPUNIT_ASSERT(diag.level() >= DiagLevel::Critical);
257  CPPUNIT_ASSERT_EQUAL("Rewriting the file to apply changed tag information failed."s, diag.front().message());
258  CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, diag.back().message());
259  }
260 
261  // restore after io failure
262  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
263  try {
264  throwIoFailure("simulated IO failure");
265  } catch (...) {
266  Diagnostics diag;
267  try {
268  handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, diag, "test");
269  CPPUNIT_FAIL("IO failure not rethrown");
270  } catch (...) {
271  catchIoFailure();
272  }
273  CPPUNIT_ASSERT(diag.level() >= DiagLevel::Critical);
274  CPPUNIT_ASSERT_EQUAL("An IO error occured when rewriting the file to apply changed tag information."s, diag.front().message());
275  CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, diag.back().message());
276  }
277 
278  CPPUNIT_ASSERT_EQUAL(0, remove(file.path().data()));
279 }
280 #endif
The Margin class defines the four margins of a rectangle.
Definition: margin.h:16
std::string toString(const std::function< TagTargetLevel(uint64)> &tagTargetMapping) const
Returns the string representation of the current instance.
Definition: tagtarget.h:201
const char * abbreviation() const
Returns an abbreviation for the current instance, eg.
Definition: size.cpp:9
void setWidth(uint32 value)
Sets the width.
Definition: size.h:75
void tearDown()
Definition: utils.cpp:69
uint64 level() const
Returns the level.
Definition: tagtarget.h:72
STL namespace.
void testMargin()
Definition: utils.cpp:126
TAG_PARSER_EXPORT std::string & backupDirectory()
Returns the directory used to store backup files.
The UtilitiesTests class tests various utility classes and functions of the tagparser library...
Definition: utils.cpp:34
void testAspectRatio()
Definition: utils.cpp:134
void setHeight(uint32 value)
Sets the height.
Definition: size.h:83
The Size class defines the size of a two-dimensional object using integer point precision.
Definition: size.h:16
Contains utility classes helping to read and write streams.
DiagLevel level() const
Definition: diagnostics.cpp:32
TAG_PARSER_EXPORT const char * containerFormatName(ContainerFormat containerFormat)
Returns the name of the specified container format as C-style string.
Definition: signature.cpp:361
const char * extensionName() const
Returns the abbreviation of the media format as C-style string.
bool isEmpty() const
Returns an indication whether the target is empty.
Definition: tagtarget.h:168
TAG_PARSER_EXPORT void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT const char * containerFormatAbbreviation(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown, unsigned int version=0)
Returns the abbreviation of the container format as C-style string considering the specified media ty...
Definition: signature.cpp:243
void testMediaFormat()
Definition: utils.cpp:148
CPPUNIT_TEST_SUITE_REGISTRATION(UtilitiesTests)
const char * shortAbbreviation() const
Returns a short abbreviation of the media format as C-style string.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
const char * name() const
Returns the name of the media format as C-style string.
Definition: mediaformat.cpp:17
void testTagTarget()
Definition: utils.cpp:87
const char * abbreviation() const
Returns the abbreviation of the media format as C-style string.
void setUp()
Definition: utils.cpp:65
std::string toString() const
Returns the string representation of the current size.
Definition: size.h:124
void testSize()
Definition: utils.cpp:73
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
Definition: signature.cpp:100
void testSignature()
Definition: utils.cpp:110
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT const char * containerFormatSubversion(ContainerFormat containerFormat)
Returns the subversion of the container format as C-style string.
Definition: signature.cpp:462