Tag Parser: mp4/mp4container.cpp Source File

Tag Parser 12.5.2 C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags

Loading...
Searching...
No Matches
mp4container.cpp
Go to the documentation of this file.
1#include "./mp4container.h"
2#include "./mp4ids.h"
3
4#include "../backuphelper.h"
5#include "../exceptions.h"
6#include "../mediafileinfo.h"
7
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/io/binaryreader.h>
10#include <c++utilities/io/binarywriter.h>
11#include <c++utilities/io/copy.h>
12#include <c++utilities/io/path.h>
13
14#include <filesystem>
15#include <memory>
16#include <numeric>
17#include <tuple>
18
19using namespace std;
20using namespace CppUtilities;
21
22namespace TagParser {
23
28
32const CppUtilities::DateTime Mp4Container::epoch = DateTime::fromDate(1904, 1, 1);
33
37Mp4Container::Mp4Container(MediaFileInfo &fileInfo, std::uint64_t startOffset)
38 : GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>(fileInfo, startOffset)
39 , m_fragmented(false)
40{
41}
42
43Mp4Container::~Mp4Container()
44{
45}
46
47 void Mp4Container::reset()
48{
49 GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>::reset();
50 m_fragmented = false;
51}
52
53 ElementPosition Mp4Container::determineTagPosition(Diagnostics &diag) const
54{
55 if (m_firstElement) {
56 const Mp4Atom *mediaDataAtom = m_firstElement->siblingById(Mp4AtomIds::MediaData, diag);
57 const Mp4Atom *userDataAtom = m_firstElement->subelementByPath(diag, Mp4AtomIds::Movie, Mp4AtomIds::UserData);
58 if (mediaDataAtom && userDataAtom) {
59 return userDataAtom->startOffset() < mediaDataAtom->startOffset() ? ElementPosition::BeforeData : ElementPosition::AfterData;
60 }
61 }
62 return ElementPosition::Keep;
63}
64
65 ElementPosition Mp4Container::determineIndexPosition(Diagnostics &diag) const
66{
67 if (m_firstElement) {
68 const Mp4Atom *mediaDataAtom = m_firstElement->siblingById(Mp4AtomIds::MediaData, diag);
69 const Mp4Atom *movieAtom = m_firstElement->siblingById(Mp4AtomIds::Movie, diag);
70 if (mediaDataAtom && movieAtom) {
71 return movieAtom->startOffset() < mediaDataAtom->startOffset() ? ElementPosition::BeforeData : ElementPosition::AfterData;
72 }
73 }
74 return ElementPosition::Keep;
75}
76
77 void Mp4Container::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
78{
79 CPP_UTILITIES_UNUSED(progress) //const string context("parsing header of MP4 container"); will be used when generating notifications
80 m_firstElement = make_unique<Mp4Atom>(*this, startOffset());
81 m_firstElement->parse(diag);
82 auto *const ftypAtom = m_firstElement->siblingByIdIncludingThis(Mp4AtomIds::FileType, diag);
83 if (!ftypAtom) {
84 m_doctype.clear();
85 m_version = 0;
86 return;
87 }
88 stream().seekg(static_cast<iostream::off_type>(ftypAtom->dataOffset()));
89 m_doctype = reader().readString(4);
90 m_version = reader().readUInt32BE();
91}
92
93 void Mp4Container::internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
94{
95 CPP_UTILITIES_UNUSED(progress)
96 const string context("parsing tags of MP4 container");
97 auto *const udtaAtom = firstElement()->subelementByPath(diag, Mp4AtomIds::Movie, Mp4AtomIds::UserData);
98 if (!udtaAtom) {
99 return;
100 }
101 auto *metaAtom = udtaAtom->childById(Mp4AtomIds::Meta, diag);
102 bool surplusMetaAtoms = false;
103 while (metaAtom) {
104 metaAtom->parse(diag);
105 m_tags.emplace_back(make_unique<Mp4Tag>());
106 try {
107 m_tags.back()->parse(*metaAtom, diag);
108 } catch (const NoDataFoundException &) {
109 m_tags.pop_back();
110 }
111 if ((metaAtom = metaAtom->siblingById(Mp4AtomIds::Meta, diag))) {
112 surplusMetaAtoms = true;
113 }
114 if (!m_tags.empty()) {
115 break;
116 }
117 }
118 if (surplusMetaAtoms) {
119 diag.emplace_back(DiagLevel::Warning, "udta atom contains multiple meta atoms. Surplus meta atoms will be ignored.", context);
120 }
121}
122
123 void Mp4Container::internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
124{
125 static const string context("parsing tracks of MP4 container");
126 try {
127 // get moov atom which holds track information
128 if (Mp4Atom *moovAtom = firstElement()->siblingByIdIncludingThis(Mp4AtomIds::Movie, diag)) {
129 // get mvhd atom which holds overall track information
130 if (Mp4Atom *mvhdAtom = moovAtom->childById(Mp4AtomIds::MovieHeader, diag)) {
131 if (mvhdAtom->dataSize() > 0) {
132 stream().seekg(static_cast<iostream::off_type>(mvhdAtom->dataOffset()));
133 std::uint8_t version = reader().readByte();
134 if ((version == 1 && mvhdAtom->dataSize() >= 32) || (mvhdAtom->dataSize() >= 20)) {
135 stream().seekg(3, ios_base::cur); // skip flags
136 switch (version) {
137 case 0:
138 m_creationTime = epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(reader().readUInt32BE()));
139 m_modificationTime = epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(reader().readUInt32BE()));
140 m_timeScale = reader().readUInt32BE();
141 m_duration = TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(reader().readUInt32BE()))
142 / static_cast<TimeSpan::TickType>(m_timeScale);
143 break;
144 case 1:
145 m_creationTime = epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(reader().readUInt64BE()));
146 m_modificationTime = epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(reader().readUInt64BE()));
147 m_timeScale = reader().readUInt32BE();
148 m_duration = TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(reader().readUInt64BE()))
149 / static_cast<TimeSpan::TickType>(m_timeScale);
150 break;
151 default:;
152 }
153 } else {
154 diag.emplace_back(DiagLevel::Critical, "mvhd atom is truncated.", context);
155 }
156 } else {
157 diag.emplace_back(DiagLevel::Critical, "mvhd atom is empty.", context);
158 }
159 } else {
160 diag.emplace_back(DiagLevel::Critical, "mvhd atom is does not exist.", context);
161 }
162 // get mvex atom which holds default values for fragmented files
163 if (Mp4Atom *mehdAtom = moovAtom->subelementByPath(diag, Mp4AtomIds::MovieExtends, Mp4AtomIds::MovieExtendsHeader)) {
164 m_fragmented = true;
165 if (mehdAtom->dataSize() > 0) {
166 stream().seekg(static_cast<iostream::off_type>(mehdAtom->dataOffset()));
167 unsigned int durationSize = reader().readByte() == 1u ? 8u : 4u; // duration size depends on atom version
168 if (mehdAtom->dataSize() >= 4 + durationSize) {
169 stream().seekg(3, ios_base::cur); // skip flags
170 switch (durationSize) {
171 case 4u:
172 m_duration = TimeSpan::fromSeconds(static_cast<double>(reader().readUInt32BE()) / static_cast<double>(m_timeScale));
173 break;
174 case 8u:
175 m_duration = TimeSpan::fromSeconds(static_cast<double>(reader().readUInt64BE()) / static_cast<double>(m_timeScale));
176 break;
177 default:;
178 }
179 } else {
180 diag.emplace_back(DiagLevel::Warning, "mehd atom is truncated.", context);
181 }
182 }
183 }
184 // get first trak atoms which hold information for each track
185 Mp4Atom *trakAtom = moovAtom->childById(Mp4AtomIds::Track, diag);
186 int trackNum = 1;
187 while (trakAtom) {
188 try {
189 trakAtom->parse(diag);
190 } catch (const Failure &) {
191 diag.emplace_back(DiagLevel::Warning, "Unable to parse child atom of moov.", context);
192 }
193 // parse the trak atom using the Mp4Track class
194 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
195 try { // try to parse header
196 m_tracks.back()->parseHeader(diag, progress);
197 } catch (const Failure &) {
198 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse track ", trackNum, '.'), context);
199 }
200 trakAtom = trakAtom->siblingById(Mp4AtomIds::Track, diag); // get next trak atom
201 ++trackNum;
202 }
203 // get overall duration, creation time and modification time if not determined yet
204 if (m_duration.isNull() || m_modificationTime.isNull() || m_creationTime.isNull()) {
205 for (const auto &track : tracks()) {
206 if (track->duration() > m_duration) {
207 m_duration = track->duration();
208 }
209 if (track->modificationTime() > m_modificationTime) {
210 m_modificationTime = track->modificationTime();
211 }
212 if (track->creationTime() < m_creationTime) {
213 m_creationTime = track->creationTime();
214 }
215 }
216 }
217 }
218 } catch (const Failure &) {
219 diag.emplace_back(DiagLevel::Warning, "Unable to parse moov atom.", context);
220 }
221}
222
223 void Mp4Container::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress)
224{
225 static const string context("making MP4 container");
226 progress.updateStep("Calculating atom sizes and padding ...");
227
228 // basic validation of original file
229 if (!isHeaderParsed()) {
230 diag.emplace_back(DiagLevel::Critical, "The header has not been parsed yet.", context);
231 throw InvalidDataException();
232 }
233
234 // define variables needed to parse atoms of original file
235 if (!firstElement()) {
236 diag.emplace_back(DiagLevel::Critical, "No MP4 atoms could be found.", context);
237 throw InvalidDataException();
238 }
239
240 // define variables needed to manage file layout
241 // -> whether media data is written chunk by chunk (need to write chunk by chunk if tracks have been altered)
242 const bool writeChunkByChunk = m_tracksAltered;
243 // -> whether rewrite is required (always required when forced to rewrite or when tracks have been altered)
244 bool rewriteRequired = fileInfo().isForcingRewrite() || writeChunkByChunk || !fileInfo().saveFilePath().empty();
245 // -> use the preferred tag position/index position (force one wins, if both are force tag pos wins; might be changed later if none is forced)
246 ElementPosition initialNewTagPos
247 = fileInfo().forceTagPosition() || !fileInfo().forceIndexPosition() ? fileInfo().tagPosition() : fileInfo().indexPosition();
248 ElementPosition newTagPos = initialNewTagPos;
249 // -> current tag position (determined later)
250 ElementPosition currentTagPos;
251 // -> holds new padding (before actual data)
252 std::uint64_t newPadding;
253 // -> holds new padding (after actual data)
254 std::uint64_t newPaddingEnd;
255 // -> holds current offset
256 std::uint64_t currentOffset;
257 // -> holds track information, used when writing chunk-by-chunk
258 vector<tuple<istream *, vector<std::uint64_t>, vector<std::uint64_t>>> trackInfos;
259 // -> holds offsets of media data atoms in original file, used when simply copying mdat
260 vector<std::int64_t> origMediaDataOffsets;
261 // -> holds offsets of media data atoms in new file, used when simply copying mdat
262 vector<std::int64_t> newMediaDataOffsets;
263 // -> new size of movie atom and user data atom
264 std::uint64_t movieAtomSize, userDataAtomSize;
265 // -> track count of original file
266 const auto trackCount = this->trackCount();
267 // -> media size of original file
268 auto mediaSize = std::uint64_t();
269
270 // find relevant atoms in original file and determine media size
271 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom /*, *userDataAtom*/;
272 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten = nullptr;
273 try {
274 // file type atom (mandatory)
275 if ((fileTypeAtom = firstElement()->siblingByIdIncludingThis(Mp4AtomIds::FileType, diag))) {
276 // buffer atom
277 fileTypeAtom->makeBuffer();
278 } else {
279 // throw error if missing
280 diag.emplace_back(DiagLevel::Critical, "Mandatory \"ftyp\"-atom not found in the source file.", context);
281 throw InvalidDataException();
282 }
283
284 // progressive download information atom (not mandatory)
285 if ((progressiveDownloadInfoAtom = firstElement()->siblingByIdIncludingThis(Mp4AtomIds::ProgressiveDownloadInformation, diag))) {
286 // buffer atom
287 progressiveDownloadInfoAtom->makeBuffer();
288 }
289
290 // movie atom (mandatory)
291 if (!(movieAtom = firstElement()->siblingByIdIncludingThis(Mp4AtomIds::Movie, diag))) {
292 // throw error if missing
293 diag.emplace_back(DiagLevel::Critical, "Mandatory \"moov\"-atom not found in the source file.", context);
294 throw InvalidDataException();
295 }
296
297 // movie fragment atom (indicates dash file)
298 if ((firstMovieFragmentAtom = firstElement()->siblingById(Mp4AtomIds::MovieFragment, diag))) {
299 // there is at least one movie fragment atom -> consider file being dash
300 // -> can not write chunk-by-chunk (currently)
301 if (writeChunkByChunk) {
302 diag.emplace_back(DiagLevel::Critical, "Writing chunk-by-chunk is not implemented for DASH files.", context);
303 throw NotImplementedException();
304 }
305 // -> tags must be placed at the beginning
306 newTagPos = ElementPosition::BeforeData;
307 }
308
309 // media data atom (mandatory?)
310 // -> consider not only mdat as media data atom; consider everything not handled otherwise as media data
311 for (firstMediaDataAtom = nullptr, level0Atom = firstElement(); level0Atom; level0Atom = level0Atom->nextSibling()) {
312 level0Atom->parse(diag);
313 switch (level0Atom->id()) {
314 case Mp4AtomIds::FileType:
315 case Mp4AtomIds::ProgressiveDownloadInformation:
316 case Mp4AtomIds::Movie:
317 case Mp4AtomIds::Free:
318 case Mp4AtomIds::Skip:
319 continue;
320 default:
321 if (!firstMediaDataAtom) {
322 firstMediaDataAtom = level0Atom;
323 }
324 mediaSize += level0Atom->totalSize();
325 }
326 }
327
328 // determine current tag position
329 // -> since tags are nested in the movie atom its position is relevant here
330 if (firstMediaDataAtom) {
331 currentTagPos = firstMediaDataAtom->startOffset() < movieAtom->startOffset() ? ElementPosition::AfterData : ElementPosition::BeforeData;
332 if (newTagPos == ElementPosition::Keep) {
333 newTagPos = currentTagPos;
334 }
335 } else {
336 currentTagPos = ElementPosition::Keep;
337 }
338
339 // ensure index and tags are always placed at the beginning when dealing with DASH files
340 if (firstMovieFragmentAtom) {
341 if (initialNewTagPos == ElementPosition::AfterData) {
342 diag.emplace_back(
343 DiagLevel::Warning, "Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
344 }
345 initialNewTagPos = newTagPos = ElementPosition::BeforeData;
346 }
347
348 // user data atom (currently not used)
349 //userDataAtom = movieAtom->childById(Mp4AtomIds::UserData);
350
351 } catch (const NotImplementedException &) {
352 throw;
353
354 } catch (const Failure &) {
355 // can't ignore parsing errors here
356 diag.emplace_back(DiagLevel::Critical, "Unable to parse the overall atom structure of the source file.", context);
357 throw InvalidDataException();
358 }
359
360 progress.stopIfAborted();
361
362 // calculate sizes
363 // -> size of tags
364 vector<Mp4TagMaker> tagMaker;
365 std::uint64_t tagsSize = 0;
366 tagMaker.reserve(m_tags.size());
367 for (auto &tag : m_tags) {
368 try {
369 tagMaker.emplace_back(tag->prepareMaking(diag));
370 tagsSize += tagMaker.back().requiredSize();
371 } catch (const Failure &) {
372 }
373 }
374
375 // -> size of movie atom (contains track and tag information)
376calculateMovieAtomSize:
377 movieAtomSize = userDataAtomSize = 0;
378 try {
379 // add size of children
380 for (level0Atom = movieAtom; level0Atom; level0Atom = level0Atom->siblingById(Mp4AtomIds::Movie, diag)) {
381 for (level1Atom = level0Atom->firstChild(); level1Atom; level1Atom = level1Atom->nextSibling()) {
382 level1Atom->parse(diag);
383 switch (level1Atom->id()) {
384 case Mp4AtomIds::UserData:
385 try {
386 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
387 level2Atom->parse(diag);
388 switch (level2Atom->id()) {
389 case Mp4AtomIds::Meta:
390 // ignore meta data here; it is added separately
391 break;
392 default:
393 // add size of unknown children of the user data atom
394 userDataAtomSize += level2Atom->totalSize();
395 level2Atom->makeBuffer();
396 }
397 }
398 } catch (const Failure &) {
399 // invalid children might be ignored as not mandatory
400 diag.emplace_back(
401 DiagLevel::Critical, "Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
402 }
403 break;
404 case Mp4AtomIds::Track:
405 // ignore track atoms here; they are added separately
406 break;
407 default:
408 // add size of unknown children of the movie atom
409 movieAtomSize += level1Atom->totalSize();
410 level1Atom->makeBuffer();
411 }
412 }
413 }
414
415 // add size of meta data
416 if (userDataAtomSize += tagsSize) {
417 Mp4Atom::addHeaderSize(userDataAtomSize);
418 movieAtomSize += userDataAtomSize;
419 }
420
421 // add size of track atoms
422 for (const auto &track : tracks()) {
423 movieAtomSize += track->requiredSize(diag);
424 }
425
426 // add header size
427 Mp4Atom::addHeaderSize(movieAtomSize);
428 } catch (const Failure &) {
429 // can't ignore parsing errors here
430 diag.emplace_back(DiagLevel::Critical, "Unable to parse the children of \"moov\"-atom of the source file.", context);
431 throw InvalidDataException();
432 }
433
434 progress.stopIfAborted();
435
436 // check whether there are atoms to be voided after movie next sibling (only relevant when not rewriting)
437 if (!rewriteRequired) {
438 newPaddingEnd = 0;
439 std::uint64_t currentSum = 0;
440 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->nextSibling()) {
441 level0Atom->parse(diag);
442 switch (level0Atom->id()) {
443 case Mp4AtomIds::FileType:
444 case Mp4AtomIds::ProgressiveDownloadInformation:
445 case Mp4AtomIds::Movie:
446 case Mp4AtomIds::Free:
447 case Mp4AtomIds::Skip:
448 // must void these if they occur "between" the media data
449 currentSum += level0Atom->totalSize();
450 break;
451 default:
452 newPaddingEnd += currentSum;
453 currentSum = 0;
454 lastAtomToBeWritten = level0Atom;
455 }
456 }
457 }
458
459 // calculate offset of padding
460 // -> file type atom
461 currentOffset = fileTypeAtom->totalSize();
462 // -> progressive download information atom
463 if (progressiveDownloadInfoAtom) {
464 currentOffset += progressiveDownloadInfoAtom->totalSize();
465 }
466 // -> if writing tags before data: movie atom (contains tag)
467 switch (newTagPos) {
468 case ElementPosition::BeforeData:
469 case ElementPosition::Keep:
470 currentOffset += movieAtomSize;
471 break;
472 default:;
473 }
474
475 // calculate padding if no rewrite is required; otherwise use the preferred padding
476calculatePadding:
477 if (rewriteRequired) {
478 newPadding = (fileInfo().preferredPadding() && fileInfo().preferredPadding() < 8 ? 8 : fileInfo().preferredPadding());
479 } else {
480 // check whether there is sufficiant space before the next atom
481 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->startOffset())) {
482 // there is sufficiant space
483 // -> check whether the padding is not between 1 and 7 bytes because that size range cannot be padded
484 // -> check whether the padding matches specifications
485 // min padding: says "at least ... byte should be reserved to prepend further tag info", so the padding at the end
486 // shouldn't be tanken into account (it can't be used to prepend further tag info)
487 // max padding: says "do not waste more than ... byte", so here all padding should be taken into account
488 newPadding = firstMediaDataAtom->startOffset() - currentOffset;
489 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding < fileInfo().minPadding()
490 || (newPadding + newPaddingEnd) > fileInfo().maxPadding();
491 }
492 if (rewriteRequired) {
493 // can't put the tags before media data
494 if (!firstMovieFragmentAtom && !fileInfo().forceTagPosition() && !fileInfo().forceIndexPosition()
495 && newTagPos != ElementPosition::AfterData) {
496 // writing tag before media data is not forced, its not a DASH file and tags aren't already at the end
497 // -> try to put the tags at the end
498 newTagPos = ElementPosition::AfterData;
499 rewriteRequired = false;
500 } else {
501 // writing tag before media data is forced -> rewrite the file
502 // when rewriting anyways, ensure the preferred tag position is used
503 newTagPos = initialNewTagPos == ElementPosition::Keep ? currentTagPos : initialNewTagPos;
504 }
505 // in any case: recalculate padding
506 goto calculatePadding;
507 } else {
508 // tags can be put before the media data
509 // -> ensure newTagPos is not ElementPosition::Keep
510 if (newTagPos == ElementPosition::Keep) {
511 newTagPos = ElementPosition::BeforeData;
512 }
513 }
514 }
515
516 // calculate end offset of media data and check if any of the chunk offset tables need to be converted
517 currentOffset += newPadding + mediaSize;
518 if (auto changedChunkOffsetSize = false; currentOffset > std::numeric_limits<std::uint32_t>::max()) {
519 for (auto &track : tracks()) {
520 if (track->chunkOffsetSize() < 8) {
521 diag.emplace_back(DiagLevel::Information,
522 argsToString("Chunk offset table of track ", track->id(), " will not fit new offsets (up to ", currentOffset,
523 "). It will be converted to 64-bit."),
524 context);
525 track->setChunkOffsetSize(8);
526 changedChunkOffsetSize = true;
527 }
528 }
529 if (changedChunkOffsetSize) {
530 goto calculateMovieAtomSize;
531 }
532 }
533
534 // setup stream(s) for writing
535 // -> update status
536 progress.nextStepOrStop("Preparing streams ...");
537
538 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
539 string originalPath = fileInfo().path(), backupPath;
540 NativeFileStream &outputStream = fileInfo().stream();
541 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
542 BinaryWriter outputWriter(&outputStream);
543
544 if (rewriteRequired) {
545 if (fileInfo().saveFilePath().empty()) {
546 // move current file to temp dir and reopen it as backupStream, recreate original file
547 try {
548 BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, outputStream, backupStream);
549 // recreate original file, define buffer variables
550 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
551 } catch (const std::ios_base::failure &failure) {
552 diag.emplace_back(
553 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
554 throw;
555 }
556 } else {
557 // open the current file as backupStream and create a new outputStream at the specified "save file path"
558 try {
559 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
560 backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
561 fileInfo().close();
562 outputStream.open(BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
563 } catch (const std::ios_base::failure &failure) {
564 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
565 throw;
566 }
567 }
568
569 // set backup stream as associated input stream since we need the original elements to write the new file
570 setStream(backupStream);
571
572 // TODO: reduce code duplication
573
574 } else { // !rewriteRequired
575 // ensure everything to make track atoms is buffered before altering the source file
576 for (const auto &track : tracks()) {
577 track->bufferTrackAtoms(diag);
578 }
579
580 // reopen original file to ensure it is opened for writing
581 try {
582 fileInfo().close();
583 outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
584 } catch (const std::ios_base::failure &failure) {
585 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
586 throw;
587 }
588 }
589
590 // start actual writing
591 try {
592 // write header
593 progress.nextStepOrStop("Writing header and tags ...");
594 // -> make file type atom
595 fileTypeAtom->copyBuffer(outputStream);
596 fileTypeAtom->discardBuffer();
597 // -> make progressive download info atom
598 if (progressiveDownloadInfoAtom) {
599 progressiveDownloadInfoAtom->copyBuffer(outputStream);
600 progressiveDownloadInfoAtom->discardBuffer();
601 }
602
603 // set input/output streams of each track
604 for (auto &track : tracks()) {
605 // ensure the track reads from the original file
606 if (&track->inputStream() == &outputStream) {
607 track->setInputStream(backupStream);
608 }
609 // ensure the track writes to the output file
610 track->setOutputStream(outputStream);
611 }
612
613 // write movie atom / padding and media data
614 for (std::uint8_t pass = 0; pass != 2; ++pass) {
615 if (newTagPos == (pass ? ElementPosition::AfterData : ElementPosition::BeforeData)) {
616 // define function to write tracks
617 auto tracksWritten = false;
618 const auto writeTracks = [this, &diag, &tracksWritten] {
619 if (tracksWritten) {
620 return;
621 }
622 for (auto &track : tracks()) {
623 track->makeTrack(diag);
624 }
625 tracksWritten = true;
626 };
627
628 // define function to write user data
629 auto userDataWritten = false;
630 auto writeUserData = [level0Atom, level1Atom, level2Atom, movieAtom, &userDataWritten, userDataAtomSize, &outputStream, &outputWriter,
631 &tagMaker, &diag]() mutable {
632 if (userDataWritten || !userDataAtomSize) {
633 return;
634 }
635
636 // writer user data atom header
637 Mp4Atom::makeHeader(userDataAtomSize, Mp4AtomIds::UserData, outputWriter);
638
639 // write children of user data atom
640 bool metaAtomWritten = false;
641 for (level0Atom = movieAtom; level0Atom; level0Atom = level0Atom->siblingById(Mp4AtomIds::Movie, diag)) {
642 for (level1Atom = level0Atom->childById(Mp4AtomIds::UserData, diag); level1Atom;
643 level1Atom = level1Atom->siblingById(Mp4AtomIds::UserData, diag)) {
644 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
645 switch (level2Atom->id()) {
646 case Mp4AtomIds::Meta:
647 // write meta atom
648 for (auto &maker : tagMaker) {
649 maker.make(outputStream, diag);
650 }
651 metaAtomWritten = true;
652 break;
653 default:
654 // write buffered data
655 level2Atom->copyBuffer(outputStream);
656 level2Atom->discardBuffer();
657 }
658 }
659 }
660 }
661
662 // write meta atom if not already written
663 if (!metaAtomWritten) {
664 for (auto &maker : tagMaker) {
665 maker.make(outputStream, diag);
666 }
667 }
668
669 userDataWritten = true;
670 };
671
672 // write movie atom
673 // -> write movie atom header
674 Mp4Atom::makeHeader(movieAtomSize, Mp4AtomIds::Movie, outputWriter);
675
676 // -> write children of movie atom preserving the original order
677 for (level0Atom = movieAtom; level0Atom; level0Atom = level0Atom->siblingById(Mp4AtomIds::Movie, diag)) {
678 for (level1Atom = level0Atom->firstChild(); level1Atom; level1Atom = level1Atom->nextSibling()) {
679 switch (level1Atom->id()) {
680 case Mp4AtomIds::Track:
681 writeTracks();
682 break;
683 case Mp4AtomIds::UserData:
684 writeUserData();
685 break;
686 default:
687 // write buffered data
688 level1Atom->copyBuffer(outputStream);
689 level1Atom->discardBuffer();
690 }
691 }
692 }
693
694 // -> write tracks and user data atoms if not already happened within the loop
695 writeTracks();
696 writeUserData();
697
698 } else {
699 // write padding
700 if (newPadding) {
701 // write free atom header
702 if (newPadding < numeric_limits<std::uint32_t>::max()) {
703 outputWriter.writeUInt32BE(static_cast<std::uint32_t>(newPadding));
704 outputWriter.writeUInt32BE(Mp4AtomIds::Free);
705 newPadding -= 8;
706 } else {
707 outputWriter.writeUInt32BE(1);
708 outputWriter.writeUInt32BE(Mp4AtomIds::Free);
709 outputWriter.writeUInt64BE(newPadding);
710 newPadding -= 16;
711 }
712 MediaFileInfo::writePadding(outputStream, newPadding);
713 }
714
715 // write media data
716 if (rewriteRequired) {
717 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->nextSibling()) {
718 level0Atom->parse(diag);
719 switch (level0Atom->id()) {
720 case Mp4AtomIds::FileType:
721 case Mp4AtomIds::ProgressiveDownloadInformation:
722 case Mp4AtomIds::Movie:
723 case Mp4AtomIds::Free:
724 case Mp4AtomIds::Skip:
725 break;
726 case Mp4AtomIds::MediaData:
727 if (writeChunkByChunk) {
728 // write actual data separately when writing chunk-by-chunk
729 break;
730 } else {
731 // store media data offsets when not writing chunk-by-chunk to be able to update chunk offset table
732 origMediaDataOffsets.push_back(static_cast<std::int64_t>(level0Atom->startOffset()));
733 newMediaDataOffsets.push_back(outputStream.tellp());
734 }
735 [[fallthrough]];
736 default:
737 // update status
738 progress.updateStep("Writing atom: " + level0Atom->idToString());
739 // copy atom entirely and forward status update calls
740 level0Atom->copyEntirely(outputStream, diag, &progress);
741 }
742 }
743
744 // when writing chunk-by-chunk write media data now
745 if (writeChunkByChunk) {
746 // read chunk offset and chunk size table from the old file which are required to get chunks
747 progress.updateStep("Reading chunk offsets and sizes from the original file ...");
748 trackInfos.reserve(trackCount);
749 std::uint64_t totalChunkCount = 0;
750 std::uint64_t totalMediaDataSize = 0;
751 for (auto &track : tracks()) {
752 progress.stopIfAborted();
753
754 // emplace information
755 trackInfos.emplace_back(
756 &track->inputStream(), track->readChunkOffsets(fileInfo().isForcingFullParse(), diag), track->readChunkSizes(diag));
757
758 // check whether the chunks could be parsed correctly
759 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
760 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
761 if (track->chunkCount() != chunkOffsetTable.size() || track->chunkCount() != chunkSizesTable.size()) {
762 diag.emplace_back(DiagLevel::Critical,
763 "Chunks of track " % numberToString<std::uint64_t, string>(track->id()) + " could not be parsed correctly.",
764 context);
765 }
766
767 // increase total chunk count and size
768 totalChunkCount += track->chunkCount();
769 totalMediaDataSize += std::accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), static_cast<std::uint64_t>(0u));
770 }
771
772 // write media data chunk-by-chunk
773 // -> write header of media data atom
774 Mp4Atom::addHeaderSize(totalMediaDataSize);
775 Mp4Atom::makeHeader(totalMediaDataSize, Mp4AtomIds::MediaData, outputWriter);
776
777 // -> copy chunks
778 CopyHelper<0x2000> copyHelper;
779 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
780 bool anyChunksCopied;
781 do {
782 progress.stopIfAborted();
783
784 // copy a chunk from each track
785 anyChunksCopied = false;
786 for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
787 // get source stream and tables for current track
788 auto &trackInfo = trackInfos[trackIndex];
789 istream &sourceStream = *get<0>(trackInfo);
790 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
791 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
792
793 // still chunks to be copied (of this track)?
794 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
795 // copy chunk, update entry in chunk offset table
796 sourceStream.seekg(static_cast<streamoff>(chunkOffsetTable[chunkIndexWithinTrack]));
797 chunkOffsetTable[chunkIndexWithinTrack] = static_cast<std::uint64_t>(outputStream.tellp());
798 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
799
800 // update counter / status
801 anyChunksCopied = true;
802 ++totalChunksCopied;
803 }
804 }
805
806 // incrase chunk index within track, update progress percentage
807 if (!(++chunkIndexWithinTrack % 10)) {
808 progress.updateStepPercentage(static_cast<std::uint8_t>(totalChunksCopied * 100 / totalChunkCount));
809 }
810
811 } while (anyChunksCopied);
812 }
813
814 } else {
815 // can't just skip next movie sibling
816 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->nextSibling()) {
817 level0Atom->parse(diag);
818 switch (level0Atom->id()) {
819 case Mp4AtomIds::FileType:
820 case Mp4AtomIds::ProgressiveDownloadInformation:
821 case Mp4AtomIds::Movie:
822 // must void these if they occur "between" the media data
823 outputStream.seekp(4, ios_base::cur);
824 outputWriter.writeUInt32BE(Mp4AtomIds::Free);
825 break;
826 default:
827 outputStream.seekp(static_cast<iostream::off_type>(level0Atom->totalSize()), ios_base::cur);
828 }
829 if (level0Atom == lastAtomToBeWritten) {
830 break;
831 }
832 }
833 }
834 }
835 }
836
837 // reparse what is written so far
838 progress.updateStep("Reparsing output file ...");
839 if (rewriteRequired) {
840 // report new size
841 fileInfo().reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
842 // "save as path" is now the regular path
843 if (!fileInfo().saveFilePath().empty()) {
844 fileInfo().reportPathChanged(fileInfo().saveFilePath());
845 fileInfo().setSaveFilePath(string());
846 }
847 // the outputStream needs to be reopened to be able to read again
848 outputStream.close();
849 outputStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::out | ios_base::binary);
850 setStream(outputStream);
851 } else {
852 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
853 if (newSize < fileInfo().size()) {
854 // file is smaller after the modification -> truncate
855 // -> close stream before truncating
856 outputStream.close();
857 // -> truncate file
858 auto ec = std::error_code();
859 std::filesystem::resize_file(makeNativePath(BasicFileInfo::pathForOpen(fileInfo().path())), newSize, ec);
860 if (!ec) {
861 fileInfo().reportSizeChanged(newSize);
862 } else {
863 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file: " + ec.message(), context);
864 }
865 // -> reopen the stream again
866 outputStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::out | ios_base::binary);
867 } else {
868 // file is longer after the modification -> just report new size
869 fileInfo().reportSizeChanged(newSize);
870 }
871 }
872
873 reset();
874 try {
875 parseTracks(diag, progress);
876 } catch (const OperationAbortedException &) {
877 throw;
878 } catch (const Failure &) {
879 diag.emplace_back(DiagLevel::Critical, "Unable to reparse the new file.", context);
880 throw;
881 }
882
883 if (rewriteRequired) {
884 // check whether the track count of the new file equals the track count of old file
885 if (trackCount != tracks().size()) {
886 diag.emplace_back(DiagLevel::Critical,
887 argsToString("Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (", tracks().size(),
888 ") differs from the number of tracks in the original file (", trackCount, ")."),
889 context);
890 throw Failure();
891 }
892
893 // update chunk offset table
894 if (writeChunkByChunk) {
895 progress.updateStep("Updating chunk offset table for each track ...");
896 for (size_t trackIndex = 0; trackIndex != trackCount; ++trackIndex) {
897 const auto &track = tracks()[trackIndex];
898 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
899 if (track->chunkCount() == chunkOffsetTable.size()) {
900 track->updateChunkOffsets(chunkOffsetTable);
901 } else {
902 diag.emplace_back(DiagLevel::Critical,
903 argsToString("Unable to update chunk offsets of track ", (trackIndex + 1),
904 ": Number of chunks in the output file differs from the number of chunks in the original file."),
905 context);
906 throw Failure();
907 }
908 }
909 } else {
910 progress.updateStep("Updating chunk offset table for each track ...");
911 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag, progress);
912 }
913 }
914
915 // prevent deferring final write operations (to catch and handle possible errors here)
916 outputStream.flush();
917
918 // handle errors (which might have been occurred after renaming/creating backup file)
919 } catch (...) {
920 BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, outputStream, backupStream, diag, context);
921 }
922}
923
936void Mp4Container::updateOffsets(const std::vector<std::int64_t> &oldMdatOffsets, const std::vector<std::int64_t> &newMdatOffsets, Diagnostics &diag,
937 AbortableProgressFeedback &progress)
938{
939 // do NOT invalidate the status here since this method is internally called by internalMakeFile(), just update the status
940 const string context("updating MP4 container chunk offset table");
941 if (!firstElement()) {
942 diag.emplace_back(DiagLevel::Critical, "No MP4 atoms could be found.", context);
943 throw InvalidDataException();
944 }
945 // update "base-data-offset-present" of "tfhd"-atom (NOT tested properly)
946 try {
947 for (Mp4Atom *moofAtom = firstElement()->siblingById(Mp4AtomIds::MovieFragment, diag); moofAtom;
948 moofAtom = moofAtom->siblingById(Mp4AtomIds::MovieFragment, diag)) {
949 moofAtom->parse(diag);
950 try {
951 for (Mp4Atom *trafAtom = moofAtom->childById(Mp4AtomIds::TrackFragment, diag); trafAtom;
952 trafAtom = trafAtom->siblingById(Mp4AtomIds::TrackFragment, diag)) {
953 trafAtom->parse(diag);
954 int tfhdAtomCount = 0;
955 for (Mp4Atom *tfhdAtom = trafAtom->childById(Mp4AtomIds::TrackFragmentHeader, diag); tfhdAtom;
956 tfhdAtom = tfhdAtom->siblingById(Mp4AtomIds::TrackFragmentHeader, diag)) {
957 tfhdAtom->parse(diag);
958 ++tfhdAtomCount;
959 if (tfhdAtom->dataSize() < 8) {
960 diag.emplace_back(DiagLevel::Warning, "tfhd atom is truncated.", context);
961 continue;
962 }
963 stream().seekg(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 1);
964 std::uint32_t flags = reader().readUInt24BE();
965 if (!(flags & 1)) {
966 continue;
967 }
968 if (tfhdAtom->dataSize() < 16) {
969 diag.emplace_back(DiagLevel::Warning, "tfhd atom (denoting base-data-offset-present) is truncated.", context);
970 continue;
971 }
972 stream().seekg(4, ios_base::cur); // skip track ID
973 std::uint64_t off = reader().readUInt64BE();
974 for (auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
975 ++iOld, ++iNew) {
976 if (off < static_cast<std::uint64_t>(*iOld)) {
977 continue;
978 }
979 off += static_cast<std::uint64_t>(*iNew - *iOld);
980 stream().seekp(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 8);
981 writer().writeUInt64BE(off);
982 break;
983 }
984 }
985 switch (tfhdAtomCount) {
986 case 0:
987 diag.emplace_back(DiagLevel::Warning, "traf atom doesn't contain mandatory tfhd atom.", context);
988 break;
989 case 1:
990 break;
991 default:
992 diag.emplace_back(
993 DiagLevel::Warning, "traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
994 }
995 }
996 } catch (const Failure &) {
997 diag.emplace_back(DiagLevel::Critical, "Unable to parse children of top-level atom moof.", context);
998 }
999 }
1000 } catch (const Failure &) {
1001 diag.emplace_back(DiagLevel::Critical, "Unable to parse top-level atom moof.", context);
1002 }
1003 // update each track
1004 for (auto &track : tracks()) {
1005 if (!track->isHeaderValid()) {
1006 try {
1007 track->parseHeader(diag, progress);
1008 } catch (const Failure &) {
1009 diag.emplace_back(DiagLevel::Warning,
1010 "The chunk offsets of track " % track->name() + " couldn't be updated because the track seems to be invalid..", context);
1011 throw;
1012 }
1013 }
1014 if (track->isHeaderValid()) {
1015 try {
1016 track->updateChunkOffsets(oldMdatOffsets, newMdatOffsets);
1017 } catch (const Failure &) {
1018 diag.emplace_back(DiagLevel::Warning, "The chunk offsets of track " % track->name() + " couldn't be updated.", context);
1019 throw;
1020 }
1021 }
1022 }
1023}
1024
1025} // namespace TagParser
backuphelper.h
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition progressfeedback.h:186
TagParser::AbortableProgressFeedback::stopIfAborted
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
Definition progressfeedback.h:255
TagParser::AbortableProgressFeedback::nextStepOrStop
void nextStepOrStop(const std::string &step, std::uint8_t stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
Definition progressfeedback.h:266
TagParser::AbstractContainer::m_modificationTime
CppUtilities::DateTime m_modificationTime
Definition abstractcontainer.h:113
TagParser::AbstractContainer::stream
std::iostream & stream()
Returns the related stream.
Definition abstractcontainer.h:136
TagParser::AbstractContainer::startOffset
std::uint64_t startOffset() const
Returns the start offset in the related stream.
Definition abstractcontainer.h:154
TagParser::AbstractContainer::parseTracks
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the file if not parsed yet.
Definition abstractcontainer.cpp:111
TagParser::AbstractContainer::version
std::uint64_t version() const
Returns the version if known; otherwise returns 0.
Definition abstractcontainer.h:218
TagParser::AbstractContainer::isHeaderParsed
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
Definition abstractcontainer.h:178
TagParser::AbstractContainer::setStream
void setStream(std::iostream &stream)
Sets the related stream.
Definition abstractcontainer.h:144
TagParser::AbstractContainer::m_doctype
std::string m_doctype
Definition abstractcontainer.h:107
TagParser::AbstractContainer::writer
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
Definition abstractcontainer.h:170
TagParser::AbstractContainer::track
virtual AbstractTrack * track(std::size_t index)
Returns the track with the specified index.
Definition abstractcontainer.cpp:373
TagParser::AbstractContainer::reader
CppUtilities::BinaryReader & reader()
Returns the related BinaryReader.
Definition abstractcontainer.h:162
TagParser::AbstractContainer::m_creationTime
CppUtilities::DateTime m_creationTime
Definition abstractcontainer.h:112
TagParser::AbstractContainer::m_tracksAltered
bool m_tracksAltered
Definition abstractcontainer.h:119
TagParser::AbstractContainer::m_timeScale
std::uint32_t m_timeScale
Definition abstractcontainer.h:114
TagParser::AbstractContainer::m_version
std::uint64_t m_version
Definition abstractcontainer.h:105
TagParser::AbstractContainer::m_duration
CppUtilities::TimeSpan m_duration
Definition abstractcontainer.h:111
TagParser::BasicFileInfo::reportPathChanged
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
Definition basicfileinfo.h:133
TagParser::BasicFileInfo::path
const std::string & path() const
Returns the path of the current file.
Definition basicfileinfo.h:103
TagParser::BasicFileInfo::stream
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition basicfileinfo.h:85
TagParser::BasicFileInfo::close
void close()
A possibly opened std::fstream will be closed.
Definition basicfileinfo.cpp:97
TagParser::BasicFileInfo::pathForOpen
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
Definition basicfileinfo.h:153
TagParser::BasicFileInfo::reportSizeChanged
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
Definition basicfileinfo.h:124
TagParser::BasicProgressFeedback::updateStep
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
Definition progressfeedback.h:96
TagParser::BasicProgressFeedback::updateStepPercentage
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
Definition progressfeedback.h:124
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::tracks
const std::vector< std::unique_ptr< Mp4Track > > & tracks() const
Definition genericcontainer.h:238
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::tag
Mp4Tag * tag(std::size_t index) override
Definition genericcontainer.h:164
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::track
Mp4Track * track(std::size_t index) override
Definition genericcontainer.h:176
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::m_tracks
std::vector< std::unique_ptr< Mp4Track > > m_tracks
Definition genericcontainer.h:61
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::GenericContainer
GenericContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Definition genericcontainer.h:71
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::m_firstElement
std::unique_ptr< Mp4Atom > m_firstElement
Definition genericcontainer.h:58
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::fileInfo
MediaFileInfo & fileInfo() const
Definition genericcontainer.h:114
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::firstElement
Mp4Atom * firstElement() const
Definition genericcontainer.h:132
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::m_tags
std::vector< std::unique_ptr< Mp4Tag > > m_tags
Definition genericcontainer.h:60
TagParser::GenericContainer< MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom >::trackCount
std::size_t trackCount() const override
Definition genericcontainer.h:193
TagParser::GenericContainer::reset
void reset() override
Discards all parsing results.
Definition genericcontainer.h:368
TagParser::GenericFileElement::copyEntirely
void copyEntirely(TargetStream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
Definition genericfileelement.h:877
TagParser::GenericFileElement::copyBuffer
void copyBuffer(TargetStream &targetStream)
Copies buffered data to targetStream.
Definition genericfileelement.h:907
TagParser::GenericFileElement::startOffset
std::uint64_t startOffset() const
Returns the start offset in the related stream.
Definition genericfileelement.h:266
TagParser::GenericFileElement::discardBuffer
void discardBuffer()
Discards buffered data.
Definition genericfileelement.h:896
TagParser::GenericFileElement::id
const IdentifierType & id() const
Returns the element ID.
Definition genericfileelement.h:283
TagParser::GenericFileElement::childById
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Definition genericfileelement.h:610
TagParser::GenericFileElement::nextSibling
ImplementationType * nextSibling()
Returns the next sibling of the element.
Definition genericfileelement.h:439
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition genericfileelement.h:465
TagParser::GenericFileElement::subelementByPath
ImplementationType * subelementByPath(Diagnostics &diag, IdType item)
Returns the sub element for the specified path.
Definition genericfileelement.h:525
TagParser::GenericFileElement::totalSize
std::uint64_t totalSize() const
Returns the total size of the element.
Definition genericfileelement.h:348
TagParser::GenericFileElement::parse
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
Definition genericfileelement.h:779
TagParser::GenericFileElement::makeBuffer
void makeBuffer()
Buffers the element (header and data).
Definition genericfileelement.h:886
TagParser::GenericFileElement::siblingById
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
Definition genericfileelement.h:648
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
TagParser::MediaFileInfo
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition mediafileinfo.h:83
TagParser::MediaFileInfo::isForcingRewrite
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
Definition mediafileinfo.h:558
TagParser::MediaFileInfo::setSaveFilePath
void setSaveFilePath(std::string_view saveFilePath)
Sets the "save file path".
Definition mediafileinfo.h:467
TagParser::MediaFileInfo::forceIndexPosition
bool forceIndexPosition() const
Returns whether indexPosition() is forced.
Definition mediafileinfo.h:717
TagParser::MediaFileInfo::saveFilePath
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
Definition mediafileinfo.h:448
TagParser::MediaFileInfo::maxPadding
std::size_t maxPadding() const
Returns the maximum padding to be written before the data blocks when applying changes.
Definition mediafileinfo.h:611
TagParser::MediaFileInfo::preferredPadding
std::size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
Definition mediafileinfo.h:632
TagParser::MediaFileInfo::minPadding
std::size_t minPadding() const
Returns the minimum padding to be written before the data blocks when applying changes.
Definition mediafileinfo.h:583
TagParser::MediaFileInfo::tagPosition
ElementPosition tagPosition() const
Returns the position (in the output file) where the tag information is written when applying changes.
Definition mediafileinfo.h:652
TagParser::MediaFileInfo::forceTagPosition
bool forceTagPosition() const
Returns whether tagPosition() is forced.
Definition mediafileinfo.h:677
TagParser::MediaFileInfo::writePadding
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
Definition mediafileinfo.cpp:1399
TagParser::MediaFileInfo::indexPosition
ElementPosition indexPosition() const
Returns the position (in the output file) where the index is written when applying changes.
Definition mediafileinfo.h:697
TagParser::Mp4Atom
The Mp4Atom class helps to parse MP4 files.
Definition mp4atom.h:38
TagParser::Mp4Atom::addHeaderSize
static constexpr void addHeaderSize(std::uint64_t &dataSize)
Adds the header size to the specified data size.
Definition mp4atom.h:81
TagParser::Mp4Atom::idToString
std::string idToString() const
Converts the specified atom ID to a printable string.
Definition mp4atom.h:67
TagParser::Mp4Atom::makeHeader
static void makeHeader(std::uint64_t size, std::uint32_t id, CppUtilities::BinaryWriter &writer)
Writes an MP4 atom header to the specified stream.
Definition mp4atom.cpp:171
TagParser::Mp4Container::determineIndexPosition
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
Definition mp4container.cpp:65
TagParser::Mp4Container::determineTagPosition
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
Definition mp4container.cpp:53
TagParser::Mp4Container::epoch
static const CppUtilities::DateTime epoch
Dates within MP4 tracks are expressed as the number of seconds since this date.
Definition mp4container.h:29
TagParser::Mp4Container::reset
void reset() override
Discards all parsing results.
Definition mp4container.cpp:47
TagParser::Mp4Container::Mp4Container
Mp4Container(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Constructs a new container for the specified fileInfo at the specified startOffset.
Definition mp4container.cpp:37
TagParser::Mp4Container::internalParseHeader
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
Definition mp4container.cpp:77
TagParser::Mp4Container::internalParseTags
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
Definition mp4container.cpp:93
TagParser::Mp4Container::internalParseTracks
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
Definition mp4container.cpp:123
TagParser::Mp4Container::internalMakeFile
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
Definition mp4container.cpp:223
TagParser::Mp4Container::~Mp4Container
~Mp4Container() override
Definition mp4container.cpp:43
TagParser::Mp4Tag
Implementation of TagParser::Tag for the MP4 container.
Definition mp4tag.h:97
TagParser::Mp4Track
Implementation of TagParser::AbstractTrack for the MP4 container.
Definition mp4track.h:120
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
TagParser::NotImplementedException
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition exceptions.h:60
TagParser::OperationAbortedException
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition exceptions.h:46
exceptions.h
mediafileinfo.h
mp4container.h
mp4ids.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::BackupHelper::handleFailureAfterFileModifiedCanonical
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
Definition backuphelper.cpp:241
TagParser::BackupHelper::createBackupFileCanonical
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
Definition backuphelper.cpp:199
TagParser::Mp4AtomIds::FileType
@ FileType
Definition mp4ids.h:29
TagParser::Mp4AtomIds::TrackFragmentHeader
@ TrackFragmentHeader
Definition mp4ids.h:70
TagParser::Mp4AtomIds::ProgressiveDownloadInformation
@ ProgressiveDownloadInformation
Definition mp4ids.h:53
TagParser::Mp4AtomIds::MediaData
@ MediaData
Definition mp4ids.h:33
TagParser::Mp4AtomIds::MovieFragment
@ MovieFragment
Definition mp4ids.h:45
TagParser::Mp4AtomIds::Track
@ Track
Definition mp4ids.h:73
TagParser::Mp4AtomIds::MovieHeader
@ MovieHeader
Definition mp4ids.h:48
TagParser::Mp4AtomIds::UserData
@ UserData
Definition mp4ids.h:77
TagParser::Mp4AtomIds::Movie
@ Movie
Definition mp4ids.h:46
TagParser::Mp4AtomIds::MovieExtendsHeader
@ MovieExtendsHeader
Definition mp4ids.h:37
TagParser::Mp4AtomIds::Skip
@ Skip
Definition mp4ids.h:57
TagParser::Mp4AtomIds::Free
@ Free
Definition mp4ids.h:28
TagParser::Mp4AtomIds::TrackFragment
@ TrackFragment
Definition mp4ids.h:72
TagParser::Mp4AtomIds::Meta
@ Meta
Definition mp4ids.h:38
TagParser::Mp4AtomIds::MovieExtends
@ MovieExtends
Definition mp4ids.h:47
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::ElementPosition
ElementPosition
Definition settings.h:13
TagParser::ElementPosition::Keep
@ Keep
Definition settings.h:16
TagParser::ElementPosition::BeforeData
@ BeforeData
Definition settings.h:14
TagParser::ElementPosition::AfterData
@ AfterData
Definition settings.h:15
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::DiagLevel::Information
@ Information
Definition diagnostics.h:19