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