Tag Parser  6.3.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1 #include "./mediafileinfo.h"
2 #include "./exceptions.h"
3 #include "./tag.h"
4 #include "./signature.h"
5 #include "./abstracttrack.h"
6 #include "./backuphelper.h"
7 
8 #include "./id3/id3v1tag.h"
9 #include "./id3/id3v2tag.h"
10 
11 #include "./wav/waveaudiostream.h"
12 
14 
15 #include "./adts/adtsstream.h"
16 
17 #include "./mp4/mp4container.h"
18 #include "./mp4/mp4atom.h"
19 #include "./mp4/mp4tag.h"
20 #include "./mp4/mp4ids.h"
21 #include "./mp4/mp4track.h"
22 
23 #include "./matroska/ebmlelement.h"
25 #include "./matroska/matroskatag.h"
27 
28 #include "./ogg/oggcontainer.h"
29 
30 #include "./flac/flacstream.h"
31 #include "./flac/flacmetadata.h"
32 
33 #include <c++utilities/conversion/stringconversion.h>
34 #include <c++utilities/io/catchiofailure.h>
35 #include <c++utilities/chrono/timespan.h>
36 
37 #include <unistd.h>
38 
39 #include <cstdio>
40 #include <algorithm>
41 #include <iomanip>
42 #include <ios>
43 #include <system_error>
44 #include <functional>
45 #include <memory>
46 
47 using namespace std;
48 using namespace std::placeholders;
49 using namespace IoUtilities;
50 using namespace ConversionUtilities;
51 using namespace ChronoUtilities;
52 
58 namespace Media {
59 
60 #ifdef FORCE_FULL_PARSE_DEFAULT
61 # define MEDIAINFO_CPP_FORCE_FULL_PARSE true
62 #else
63 # define MEDIAINFO_CPP_FORCE_FULL_PARSE false
64 #endif
65 
79 MediaFileInfo::MediaFileInfo() :
80  m_containerParsingStatus(ParsingStatus::NotParsedYet),
81  m_containerFormat(ContainerFormat::Unknown),
82  m_containerOffset(0),
83  m_actualExistingId3v1Tag(false),
84  m_tracksParsingStatus(ParsingStatus::NotParsedYet),
85  m_tagsParsingStatus(ParsingStatus::NotParsedYet),
86  m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
87  m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
88  m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
89  m_forceRewrite(true),
90  m_minPadding(0),
91  m_maxPadding(0),
92  m_preferredPadding(0),
93  m_tagPosition(ElementPosition::BeforeData),
94  m_forceTagPosition(true),
95  m_indexPosition(ElementPosition::BeforeData),
96  m_forceIndexPosition(true)
97 {}
98 
105  BasicFileInfo(path),
106  m_containerParsingStatus(ParsingStatus::NotParsedYet),
107  m_containerFormat(ContainerFormat::Unknown),
108  m_containerOffset(0),
109  m_actualExistingId3v1Tag(false),
110  m_tracksParsingStatus(ParsingStatus::NotParsedYet),
111  m_tagsParsingStatus(ParsingStatus::NotParsedYet),
112  m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
113  m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
114  m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
115  m_forceRewrite(true),
116  m_minPadding(0),
117  m_maxPadding(0),
118  m_preferredPadding(0),
119  m_tagPosition(ElementPosition::BeforeData),
120  m_forceTagPosition(true),
121  m_indexPosition(ElementPosition::BeforeData),
122  m_forceIndexPosition(true)
123 {}
124 
129 {}
130 
147 {
149  // there's no need to read the container format twice
150  return;
151  }
152 
154  static const string context("parsing file header");
155  open(); // ensure the file is open
156  m_containerFormat = ContainerFormat::Unknown;
157 
158  // file size
159  m_paddingSize = 0;
160  m_containerOffset = 0;
161 
162  // read signatrue
163  char buff[16];
164  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
165 startParsingSignature:
166  if(size() - m_containerOffset >= 16) {
167  stream().seekg(m_containerOffset, ios_base::beg);
168  stream().read(buff, sizeof(buff));
169 
170  // skip zero bytes/padding
171  size_t bytesSkipped = 0;
172  for(buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped);
173  if(bytesSkipped >= 4) {
174  m_containerOffset += bytesSkipped;
175 
176  // give up after 0x100 bytes
177  if((m_paddingSize += bytesSkipped) >= 0x100u) {
178  m_containerParsingStatus = ParsingStatus::NotSupported;
179  m_containerFormat = ContainerFormat::Unknown;
180  return;
181  }
182 
183  // try again
184  goto startParsingSignature;
185  }
186  if(m_paddingSize) {
187  addNotification(NotificationType::Warning, numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
188  }
189 
190  // parse signature
191  switch((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
193  // save position of ID3v2 tag
194  m_actualId3v2TagOffsets.push_back(m_containerOffset);
195  if(m_actualId3v2TagOffsets.size() == 2) {
196  addNotification(NotificationType::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
197  }
198 
199  // read ID3v2 header
200  stream().seekg(m_containerOffset + 5, ios_base::beg);
201  stream().read(buff, 5);
202 
203  // set the container offset to skip ID3v2 header
204  m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
205  if((*buff) & 0x10) {
206  // footer present
207  m_containerOffset += 10;
208  }
209 
210  // continue reading signature
211  goto startParsingSignature;
212 
215  // MP4/QuickTime is handled using Mp4Container instance
216  m_container = make_unique<Mp4Container>(*this, m_containerOffset);
218  try {
219  static_cast<Mp4Container *>(m_container.get())->validateElementStructure(notifications, &m_paddingSize);
220  } catch(const Failure &) {
221  m_containerParsingStatus = ParsingStatus::CriticalFailure;
222  }
223  addNotifications(notifications);
224  break;
225 
226  } case ContainerFormat::Ebml: {
227  // EBML/Matroska is handled using MatroskaContainer instance
228  auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
230  try {
232  if(container->documentType() == "matroska") {
233  m_containerFormat = ContainerFormat::Matroska;
234  } else if(container->documentType() == "webm") {
235  m_containerFormat = ContainerFormat::Webm;
236  }
237  if(m_forceFullParse) {
238  // validating the element structure of Matroska files takes too long when
239  // parsing big files so do this only when explicitely desired
240  container->validateElementStructure(notifications, &m_paddingSize);
241  container->validateIndex();
242  }
243  } catch(const Failure &) {
244  m_containerParsingStatus = ParsingStatus::CriticalFailure;
245  }
246  m_container = move(container);
247  addNotifications(notifications);
248  break;
249  } case ContainerFormat::Ogg:
250  // Ogg is handled using OggContainer instance
251  m_container = make_unique<OggContainer>(*this, m_containerOffset);
252  static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
253  break;
255  // container format is still unknown -> check for magic numbers at odd offsets
256  // -> check for tar (magic number at offset 0x101)
257  if(size() > 0x107) {
258  stream().seekg(0x101);
259  stream().read(buff, 6);
260  if(buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
261  m_containerFormat = ContainerFormat::Tar;
262  break;
263  }
264  }
265  default:
266  ;
267  }
268  }
269 
270  // set parsing status
271  if(m_containerParsingStatus == ParsingStatus::NotParsedYet) {
272  if(m_containerFormat == ContainerFormat::Unknown) {
273  m_containerParsingStatus = ParsingStatus::NotSupported;
274  } else {
275  m_containerParsingStatus = ParsingStatus::Ok;
276  }
277  }
278 }
279 
294 {
295  if(tracksParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the tracks twice
296  return;
297  }
298  static const string context("parsing tracks");
299  try {
300  if(m_container) {
301  m_container->parseTracks();
302  } else {
303  switch(m_containerFormat) {
305  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
306  break;
308  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
309  break;
311  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
312  break;
314  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
315  break;
316  default:
317  throw NotImplementedException();
318  }
319  m_singleTrack->parseHeader();
320 
321  switch(m_containerFormat) {
323  // FLAC streams might container padding
324  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
325  break;
326  default:
327  ;
328  }
329  }
330  m_tracksParsingStatus = ParsingStatus::Ok;
331  } catch(const NotImplementedException &) {
332  addNotification(NotificationType::Information, "Parsing tracks is not implemented for the container format of the file.", context);
333  m_tracksParsingStatus = ParsingStatus::NotSupported;
334  } catch(const Failure &) {
335  addNotification(NotificationType::Critical, "Unable to parse tracks.", context);
336  m_tracksParsingStatus = ParsingStatus::CriticalFailure;
337  }
338 }
339 
355 {
356  if(tagsParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the tags twice
357  return;
358  }
359  static const string context("parsing tag");
360  // check for id3v1 tag
361  if(size() >= 128) {
362  m_id3v1Tag = make_unique<Id3v1Tag>();
363  try {
364  m_id3v1Tag->parse(stream(), true);
365  m_actualExistingId3v1Tag = true;
366  } catch(const NoDataFoundException &) {
367  m_id3v1Tag.reset(); // no ID3v1 tag found
368  } catch(const Failure &) {
369  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
370  addNotification(NotificationType::Critical, "Unable to parse ID3v1 tag.", context);
371  }
372  }
373  // the offsets of the ID3v2 tags have already been parsed when parsing the container format
374  m_id3v2Tags.clear();
375  for(const auto offset : m_actualId3v2TagOffsets) {
376  auto id3v2Tag = make_unique<Id3v2Tag>();
377  stream().seekg(offset, ios_base::beg);
378  try {
379  id3v2Tag->parse(stream(), size() - offset);
380  m_paddingSize += id3v2Tag->paddingSize();
381  } catch(const NoDataFoundException &) {
382  continue;
383  } catch(const Failure &) {
384  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
385  addNotification(NotificationType::Critical, "Unable to parse ID3v2 tag.", context);
386  }
387  m_id3v2Tags.emplace_back(id3v2Tag.release());
388  }
389  if(m_container) {
390  try {
391  m_container->parseTags();
392  } catch(const NotImplementedException &) {
393  if(m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
394  // do not override parsing status from ID3 tags here
395  m_tagsParsingStatus = ParsingStatus::NotSupported;
396  }
397  addNotification(NotificationType::Information, "Parsing tags is not implemented for the container format of the file.", context);
398  } catch(const Failure &) {
399  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
400  addNotification(NotificationType::Critical, "Unable to parse tag.", context);
401  }
402  }
403  if(m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
404  // do not override error status here
405  m_tagsParsingStatus = ParsingStatus::Ok;
406  }
407 }
408 
421 {
422  if(chaptersParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the chapters twice
423  return;
424  }
425  static const string context("parsing chapters");
426  try {
427  if(m_container) {
428  m_container->parseChapters();
429  m_chaptersParsingStatus = ParsingStatus::Ok;
430  } else {
431  throw NotImplementedException();
432  }
433  } catch (const NotImplementedException &) {
434  m_chaptersParsingStatus = ParsingStatus::NotSupported;
435  addNotification(NotificationType::Information, "Parsing chapters is not implemented for the container format of the file.", context);
436  } catch (const Failure &) {
437  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
438  addNotification(NotificationType::Critical, "Unable to parse chapters.", context);
439  }
440 }
441 
454 {
455  if(attachmentsParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the attachments twice
456  return;
457  }
458  static const string context("parsing attachments");
459  try {
460  if(m_container) {
461  m_container->parseAttachments();
462  m_attachmentsParsingStatus = ParsingStatus::Ok;
463  } else {
464  throw NotImplementedException();
465  }
466  } catch (const NotImplementedException &) {
467  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
468  addNotification(NotificationType::Information, "Parsing attachments is not implemented for the container format of the file.", context);
469  } catch (const Failure &) {
470  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
471  addNotification(NotificationType::Critical, "Unable to parse attachments.", context);
472  }
473 }
474 
482 {
484  parseTracks();
485  parseTags();
486  parseChapters();
488 }
489 
511 bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagUsage id3v1usage, TagUsage id3v2usage, bool id3InitOnCreate, bool id3TransferValuesOnRemoval, bool mergeMultipleSuccessiveId3v2Tags, bool keepExistingId3v2version, byte id3v2MajorVersion, const std::vector<TagTarget> &requiredTargets)
512 {
513  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
515  return false;
516  }
517  // check if tags need to be created/adjusted/removed
518  bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
519  bool targetsSupported = false;
520  if(areTagsSupported() && m_container) {
521  // container object takes care of tag management
522  if(targetsRequired) {
523  // check whether container supports targets
524  if(m_container->tagCount()) {
525  // all tags in the container should support targets if the first one supports targets
526  targetsSupported = m_container->tag(0)->supportsTarget();
527  } else {
528  // try to create a new tag and check whether targets are supported
529  auto *tag = m_container->createTag();
530  if(tag) {
531  if((targetsSupported = tag->supportsTarget())) {
532  tag->setTarget(requiredTargets.front());
533  }
534  }
535  }
536  if(targetsSupported) {
537  for(const auto &target : requiredTargets) {
538  m_container->createTag(target);
539  }
540  }
541  } else {
542  // no targets are required -> just ensure that at least one tag is present
543  m_container->createTag();
544  }
545  } else {
546  // no container object present
547  if(m_containerFormat == ContainerFormat::Flac) {
548  // creation of Vorbis comment is possible
549  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
550  } else {
551  // creation of ID3 tag is possible
552  if(!hasAnyTag() && !treatUnknownFilesAsMp3Files) {
553  switch(containerFormat()) {
556  break;
557  default:
558  return false;
559  }
560  }
561  // create ID3 tags according to id3v2usage and id3v2usage
562  if(id3v1usage == TagUsage::Always) {
563  // always create ID3v1 tag -> ensure there is one
564  if(!id3v1Tag()) {
566  if(id3InitOnCreate) {
567  for(const auto &id3v2Tag : id3v2Tags()) {
568  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
569  id3v1Tag->insertValues(*id3v2Tag, true);
570  // ID3v1 does not support all text encodings which might be used in ID3v2
572  }
573  }
574  }
575  }
576  if(id3v2usage == TagUsage::Always) {
577  // always create ID3v2 tag -> ensure there is one and set version
578  if(!hasId3v2Tag()) {
579  Id3v2Tag *id3v2Tag = createId3v2Tag();
580  id3v2Tag->setVersion(id3v2MajorVersion, 0);
581  if(id3InitOnCreate && id3v1Tag()) {
582  id3v2Tag->insertValues(*id3v1Tag(), true);
583  }
584  }
585  }
586  }
587 
588  if(mergeMultipleSuccessiveId3v2Tags) {
589  mergeId3v2Tags();
590  }
591  // remove ID3 tags according to id3v2usage and id3v2usage
592  if(id3v1usage == TagUsage::Never) {
593  if(hasId3v1Tag()) {
594  // transfer tags to ID3v2 tag before removing
595  if(id3TransferValuesOnRemoval && hasId3v2Tag()) {
596  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
597  }
598  removeId3v1Tag();
599  }
600  }
601  if(id3v2usage == TagUsage::Never) {
602  if(id3TransferValuesOnRemoval && hasId3v1Tag()) {
603  // transfer tags to ID3v1 tag before removing
604  for(const auto &tag : id3v2Tags()) {
605  id3v1Tag()->insertValues(*tag, false);
606  }
607  }
609  } else if(!keepExistingId3v2version) {
610  // set version of ID3v2 tag according user preferences
611  for(const auto &tag : id3v2Tags()) {
612  tag->setVersion(id3v2MajorVersion, 0);
613  }
614  }
615  }
616  if(targetsRequired && !targetsSupported) {
617  addNotification(NotificationType::Information, "The container/tags do not support targets. The specified targets are ignored.", "creating tags");
618  }
619  return true;
620 }
621 
622 
645 {
646  const string context("making file");
647  addNotification(NotificationType::Information, "Changes are about to be applied.", context);
648  bool previousParsingSuccessful = true;
649  switch(tagsParsingStatus()) {
650  case ParsingStatus::Ok:
652  break;
653  default:
654  previousParsingSuccessful = false;
655  addNotification(NotificationType::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
656  }
657  switch(tracksParsingStatus()) {
658  case ParsingStatus::Ok:
660  break;
661  default:
662  previousParsingSuccessful = false;
663  addNotification(NotificationType::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
664  }
665  if(!previousParsingSuccessful) {
666  throw InvalidDataException();
667  }
668  if(m_container) { // container object takes care
669  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
670  if(hasId3v1Tag()) {
671  addNotification(NotificationType::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
672  }
673  if(hasId3v2Tag()) {
674  addNotification(NotificationType::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
675  }
676  m_container->forwardStatusUpdateCalls(this);
677  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
678  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
679  try {
680  m_container->makeFile();
681  addNotifications(*m_container);
682  } catch(...) {
683  addNotifications(*m_container);
685  throw;
686  }
687  } else { // implementation if no container object is present
688  // assume the file is a MP3 file
689  try {
690  makeMp3File();
691  } catch(...) {
693  throw;
694  }
695  }
697 }
698 
712 {
713  MediaType mediaType = MediaType::Unknown;
714  unsigned int version = 0;
715  switch(m_containerFormat) {
717  // check whether only Opus tracks are present
718  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
719  for(const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
720  if(track->format().general != GeneralMediaFormat::Opus) {
721  version = 0;
722  break;
723  }
724  }
728  break;
730  if(m_singleTrack) {
731  version = m_singleTrack->format().sub;
732  }
733  break;
734  default:
735  ;
736  }
737  return Media::containerFormatAbbreviation(m_containerFormat, mediaType, version);
738 }
739 
750 const char *MediaFileInfo::mimeType() const
751 {
752  MediaType mediaType;
753  switch(m_containerFormat) {
758  break;
759  default:
760  mediaType = MediaType::Unknown;
761  }
762  return Media::containerMimeType(m_containerFormat, mediaType);
763 }
764 
777 vector<AbstractTrack *> MediaFileInfo::tracks() const
778 {
779  vector<AbstractTrack *> res;
780  size_t trackCount = 0;
781  size_t containerTrackCount = 0;
782  if(m_singleTrack) {
783  trackCount = 1;
784  }
785  if(m_container) {
786  trackCount += (containerTrackCount = m_container->trackCount());
787  }
788  res.reserve(trackCount);
789 
790  if(m_singleTrack) {
791  res.push_back(m_singleTrack.get());
792  }
793  for(size_t i = 0; i != containerTrackCount; ++i) {
794  res.push_back(m_container->track(i));
795  }
796  return res;
797 }
798 
808 {
810  if(m_singleTrack && m_singleTrack->mediaType() == type) {
811  return true;
812  } else if(m_container) {
813  for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
814  if(m_container->track(i)->mediaType() == type) {
815  return true;
816  }
817  }
818  }
819  }
820  return false;
821 }
822 
833 {
834  TimeSpan res;
835  if(m_container) {
836  res = m_container->duration();
837  } else {
838  for(const AbstractTrack *track : tracks()) {
839  if(track->duration() > res) {
840  res = track->duration();
841  }
842  }
843  }
844  return res;
845 }
846 
857 {
859  if(m_id3v1Tag) {
860  m_id3v1Tag.reset();
861  return true;
862  }
863  }
864  return false;
865 }
866 
883 {
885  return nullptr;
886  }
887  if(!m_id3v1Tag) {
888  m_id3v1Tag = make_unique<Id3v1Tag>();
889  }
890  return m_id3v1Tag.get();
891 }
892 
907 {
909  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
910  if(i->get() == tag) {
911  m_id3v2Tags.erase(i);
912  return true;
913  }
914  }
915  }
916  return false;
917 }
918 
928 {
929  if(tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
930  return false;
931  }
932  m_id3v2Tags.clear();
933  return true;
934 }
935 
952 {
953  if(m_id3v2Tags.empty()) {
954  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
955  }
956  return m_id3v2Tags.front().get();
957 }
958 
972 {
973  if(tag) {
974  if(m_container) {
975  m_container->removeTag(tag);
976  }
977  if(m_id3v1Tag.get() == tag) {
978  m_id3v1Tag.reset();
979  }
980  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
981  if(i->get() == tag) {
982  m_id3v2Tags.erase(i);
983  break;
984  }
985  }
986  }
987 }
988 
995 {
996  if(m_container) {
997  m_container->removeAllTags();
998  }
999  if(m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1000  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1001  }
1002  m_id3v1Tag.reset();
1003  m_id3v2Tags.clear();
1004 }
1005 
1010 {
1011  if(m_container && m_container->chapterCount()) {
1012  return true;
1013  }
1014  switch(m_containerFormat) {
1016  case ContainerFormat::Webm:
1017  return true;
1018  default:
1019  return false;
1020  }
1021 }
1022 
1027 {
1028  if(m_container && m_container->attachmentCount()) {
1029  return true;
1030  }
1031  switch(m_containerFormat) {
1033  case ContainerFormat::Webm:
1034  return true;
1035  default:
1036  return false;
1037  }
1038 }
1039 
1044 {
1045  if(trackCount()) {
1046  return true;
1047  }
1048  switch(m_containerFormat) {
1049  case ContainerFormat::Mp4:
1052  case ContainerFormat::Ogg:
1054  case ContainerFormat::Webm:
1055  return true;
1056  default:
1057  return false;
1058  }
1059 }
1060 
1065 {
1066  switch(m_containerFormat) {
1067  case ContainerFormat::Adts:
1068  case ContainerFormat::Flac:
1071  case ContainerFormat::Mp4:
1072  case ContainerFormat::Ogg:
1073  case ContainerFormat::Webm:
1074  // these container formats are supported
1075  return true;
1076  default:
1077  // the container format is unsupported
1078  // -> an ID3 tag might be already present, in this case the tags are considered supported
1079  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1080  }
1081 }
1082 
1089 {
1090  // simply return the first tag here since MP4 files never contain multiple tags
1091  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container && m_container->tagCount() > 0 ? static_cast<Mp4Container *>(m_container.get())->tags().front().get() : nullptr;
1092 }
1093 
1100 const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
1101 {
1102  // matroska files might contain multiple tags (targeting different scopes)
1103  if(m_containerFormat == ContainerFormat::Matroska && m_container) {
1104  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1105  } else {
1106  static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1107  return empty;
1108  }
1109 }
1110 
1117 {
1118  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1119  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1120  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack
1121  ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()
1122  : nullptr);
1123 }
1124 
1130 vector<AbstractChapter *> MediaFileInfo::chapters() const
1131 {
1132  vector<AbstractChapter *> res;
1133  if(m_container) {
1134  size_t count = m_container->chapterCount();
1135  res.reserve(count);
1136  for(size_t i = 0; i != count; ++i) {
1137  res.push_back(m_container->chapter(i));
1138  }
1139  }
1140  return res;
1141 }
1142 
1148 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1149 {
1150  vector<AbstractAttachment *> res;
1151  if(m_container) {
1152  size_t count = m_container->attachmentCount();
1153  res.reserve(count);
1154  for(size_t i = 0; i < count; ++i) {
1155  res.push_back(m_container->attachment(i));
1156  }
1157  }
1158  return res;
1159 }
1160 
1166 {
1167  if(m_container) {
1168  if(m_container->hasNotifications()) {
1169  return true;
1170  }
1171  }
1172  for(const auto *track : tracks()) {
1173  if(track->hasNotifications()) {
1174  return true;
1175  }
1176  }
1177  for(const auto *tag : tags()) {
1178  if(tag->hasNotifications()) {
1179  return true;
1180  }
1181  }
1182  for(const auto *chapter : chapters()) {
1183  if(chapter->hasNotifications()) {
1184  return true;
1185  }
1186  }
1187  return false;
1188 }
1189 
1195 {
1197  if(type == Notification::worstNotificationType()) {
1198  return type;
1199  }
1200  if(m_container) {
1201  type |= m_container->worstNotificationType();
1202  }
1203  if(type == Notification::worstNotificationType()) {
1204  return type;
1205  }
1206  for(const auto *track : tracks()) {
1207  type |= track->worstNotificationType();
1208  if(type == Notification::worstNotificationType()) {
1209  return type;
1210  }
1211  }
1212  for(const auto *tag : tags()) {
1213  type |= tag->worstNotificationType();
1214  if(type == Notification::worstNotificationType()) {
1215  return type;
1216  }
1217  }
1218  for(const auto *chapter : chapters()) {
1219  type |= chapter->worstNotificationType();
1220  if(type == Notification::worstNotificationType()) {
1221  return type;
1222  }
1223  }
1224  return type;
1225 }
1226 
1233 {
1234  notifications.insert(notifications.end(), this->notifications().cbegin(), this->notifications().cend());
1235  if(m_container) {
1236  notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
1237  }
1238  for(const auto *track : tracks()) {
1239  notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1240  }
1241  for(const auto *tag : tags()) {
1242  notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1243  }
1244  for(const auto *chapter : chapters()) {
1245  notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1246  }
1247  for(const auto *attachment : attachments()) {
1248  notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1249  }
1250 }
1251 
1257 {
1259  gatherRelatedNotifications(notifications);
1260  return notifications;
1261 }
1262 
1273 {
1274  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1275  m_containerFormat = ContainerFormat::Unknown;
1276  m_containerOffset = 0;
1277  m_paddingSize = 0;
1278  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1279  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1280  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1281  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1282  m_id3v1Tag.reset();
1283  m_id3v2Tags.clear();
1284  m_actualId3v2TagOffsets.clear();
1285  m_actualExistingId3v1Tag = false;
1286  m_container.reset();
1287  m_singleTrack.reset();
1288 }
1289 
1307 {
1308  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1309  if(begin != end) {
1310  Id3v2Tag &first = **begin;
1311  auto isecond = begin + 1;
1312  if(isecond != end) {
1313  for(auto i = isecond; i != end; ++i) {
1314  first.insertFields(**i, false);
1315  }
1316  m_id3v2Tags.erase(isecond, end - 1);
1317  }
1318  }
1319 }
1320 
1333 {
1334  if(!areTagsSupported() || !m_container) {
1335  return createAppropriateTags(false, TagUsage::Never, TagUsage::Always, true, true, 3);
1336  } else {
1337  return false;
1338  }
1339 }
1340 
1353 {
1354  if(!areTagsSupported() || !m_container) {
1355  return createAppropriateTags(false, TagUsage::Always, TagUsage::Never, true, true, 3);
1356  } else {
1357  return false;
1358  }
1359 }
1360 
1376 {
1377  switch(m_containerFormat) {
1378  case ContainerFormat::Ogg:
1379  if(m_container) {
1380  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1381  }
1382  break;
1383  case ContainerFormat::Flac:
1384  if(m_singleTrack) {
1385  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1386  }
1387  break;
1388  default:
1389  ;
1390  }
1391  return nullptr;
1392 }
1393 
1404 {
1405  switch(m_containerFormat) {
1406  case ContainerFormat::Ogg:
1407  if(m_container) {
1408  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1409  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1410  return hadTags;
1411  }
1412  break;
1413  case ContainerFormat::Flac:
1414  if(m_singleTrack) {
1415  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1416  }
1417  break;
1418  default:
1419  ;
1420  }
1421  return false;
1422 }
1423 
1432 void MediaFileInfo::tags(vector<Tag *> &tags) const
1433 {
1434  if(hasId3v1Tag()) {
1435  tags.push_back(m_id3v1Tag.get());
1436  }
1437  for(const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1438  tags.push_back(tag.get());
1439  }
1440  if(m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1441  if(auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1442  tags.push_back(vorbisComment);
1443  }
1444  }
1445  if(m_container) {
1446  for(size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1447  tags.push_back(m_container->tag(i));
1448  }
1449  }
1450 }
1451 
1456 {
1457  return hasId3v1Tag()
1458  || hasId3v2Tag()
1459  || (m_container && m_container->tagCount())
1460  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1461 }
1462 
1469 vector<Tag *> MediaFileInfo::tags() const
1470 {
1471  vector<Tag *> res;
1472  tags(res);
1473  return res;
1474 }
1475 
1480 {
1482  invalidateStatus();
1485 }
1486 
1490 void MediaFileInfo::makeMp3File()
1491 {
1492  static const string context("making MP3/FLAC file");
1493  // there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
1494  if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty() && m_containerFormat != ContainerFormat::Flac) {
1495  if(m_actualExistingId3v1Tag) {
1496  // there is currently an ID3v1 tag at the end of the file
1497  if(m_id3v1Tag) {
1498  // the file shall still have an ID3v1 tag
1499  updateStatus("Updating ID3v1 tag ...");
1500  // ensure the file is still open / not readonly
1501  open();
1502  stream().seekp(-128, ios_base::end);
1503  try {
1504  m_id3v1Tag->make(stream());
1505  } catch(const Failure &) {
1506  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1507  }
1508  } else {
1509  // the currently existing ID3v1 tag shall be removed
1510  updateStatus("Removing ID3v1 tag ...");
1511  stream().close();
1512  if(truncate(path().c_str(), size() - 128) == 0) {
1513  reportSizeChanged(size() - 128);
1514  } else {
1515  addNotification(NotificationType::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1516  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1517  }
1518  }
1519 
1520  } else {
1521  // there is currently no ID3v1 tag at the end of the file
1522  if(m_id3v1Tag) {
1523  updateStatus("Adding ID3v1 tag ...");
1524  // ensure the file is still open / not readonly
1525  open();
1526  stream().seekp(0, ios_base::end);
1527  try {
1528  m_id3v1Tag->make(stream());
1529  } catch(const Failure &) {
1530  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1531  }
1532  } else {
1533  addNotification(NotificationType::Information, "Nothing to be changed.", context);
1534  }
1535  }
1536 
1537  } else {
1538  // ID3v2 needs to be modified
1539  updateStatus("Updating ID3v2 tags ...");
1540 
1541  // prepare ID3v2 tags
1542  vector<Id3v2TagMaker> makers;
1543  makers.reserve(m_id3v2Tags.size());
1544  uint32 tagsSize = 0;
1545  for(auto &tag : m_id3v2Tags) {
1546  try {
1547  makers.emplace_back(tag->prepareMaking());
1548  tagsSize += makers.back().requiredSize();
1549  } catch(const Failure &) {
1550  // nothing to do: notifications added anyways
1551  }
1552  addNotifications(*tag);
1553  }
1554 
1555  // check whether it is a raw FLAC stream
1556  FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1557  uint32 streamOffset; // where the actual stream starts
1558  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1559  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1560  uint32 startOfLastMetaDataBlock;
1561 
1562  if(flacStream) {
1563  // if it is a raw FLAC stream, make FLAC metadata
1564  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData);
1565  tagsSize += flacMetaData.tellp();
1566  streamOffset = flacStream->streamOffset();
1567  } else {
1568  // make no further metadata, just use the container offset as stream offset
1569  streamOffset = static_cast<uint32>(m_containerOffset);
1570  }
1571 
1572  // check whether rewrite is required
1573  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1574  uint32 padding = 0;
1575  if(!rewriteRequired) {
1576  // rewriting is not forced and new tag is not too big for available space
1577  // -> calculate new padding
1578  padding = streamOffset - tagsSize;
1579  // -> check whether the new padding matches specifications
1580  if(padding < minPadding() || padding > maxPadding()) {
1581  rewriteRequired = true;
1582  }
1583  }
1584  if(makers.empty() && !flacStream) {
1585  // an ID3v2 tag is not written and it is not a FLAC stream
1586  // -> can't include padding
1587  if(padding) {
1588  // but padding would be present -> need to rewrite
1589  padding = 0; // can't write the preferred padding despite rewriting
1590  rewriteRequired = true;
1591  }
1592  } else if(rewriteRequired) {
1593  // rewriting is forced or new ID3v2 tag is too big for available space
1594  // -> use preferred padding when rewriting anyways
1595  padding = preferredPadding();
1596  } else if(makers.empty() && flacStream && padding && padding < 4) {
1597  // no ID3v2 tag -> must include padding in FLAC stream
1598  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1599  padding = preferredPadding();
1600  rewriteRequired = true;
1601  }
1602  if(rewriteRequired && flacStream && makers.empty() && padding) {
1603  // the first 4 byte of FLAC padding actually don't count because these
1604  // can not be used for additional meta data
1605  padding += 4;
1606  }
1607  updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1608 
1609  // setup stream(s) for writing
1610  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1611  string backupPath;
1612  NativeFileStream &outputStream = stream();
1613  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1614 
1615  if(rewriteRequired) {
1616  if(m_saveFilePath.empty()) {
1617  // move current file to temp dir and reopen it as backupStream, recreate original file
1618  try {
1619  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1620  // recreate original file, define buffer variables
1621  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1622  } catch(...) {
1623  const char *what = catchIoFailure();
1624  addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1625  throwIoFailure(what);
1626  }
1627  } else {
1628  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1629  try {
1630  close();
1631  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1632  backupStream.open(path(), ios_base::in | ios_base::binary);
1633  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1634  } catch(...) {
1635  const char *what = catchIoFailure();
1636  addNotification(NotificationType::Critical, "Opening streams to write output file failed.", context);
1637  throwIoFailure(what);
1638  }
1639  }
1640 
1641  } else { // !rewriteRequired
1642  // reopen original file to ensure it is opened for writing
1643  try {
1644  close();
1645  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1646  } catch(...) {
1647  const char *what = catchIoFailure();
1648  addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
1649  throwIoFailure(what);
1650  }
1651  }
1652 
1653  // start actual writing
1654  try {
1655  if(!makers.empty()) {
1656  // write ID3v2 tags
1657  updateStatus("Writing ID3v2 tag ...");
1658  for(auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1659  i->make(outputStream, 0);
1660  }
1661  // include padding into the last ID3v2 tag
1662  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1663  }
1664 
1665  if(flacStream) {
1666  if(padding && startOfLastMetaDataBlock) {
1667  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1668  flacMetaData.seekg(startOfLastMetaDataBlock);
1669  flacMetaData.seekp(startOfLastMetaDataBlock);
1670  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1671  flacMetaData.seekg(0);
1672  }
1673 
1674  // write FLAC metadata
1675  outputStream << flacMetaData.rdbuf();
1676 
1677  // write padding
1678  if(padding) {
1679  flacStream->makePadding(outputStream, padding, true);
1680  }
1681  }
1682 
1683  if(makers.empty() && !flacStream){
1684  // just write padding (however, padding should be set to 0 in this case?)
1685  for(; padding; --padding) {
1686  outputStream.put(0);
1687  }
1688  }
1689 
1690  // copy / skip actual stream data
1691  // -> determine media data size
1692  uint64 mediaDataSize = size() - streamOffset;
1693  if(m_actualExistingId3v1Tag) {
1694  mediaDataSize -= 128;
1695  }
1696 
1697  if(rewriteRequired) {
1698  // copy data from original file
1699  switch(m_containerFormat) {
1701  updateStatus("Writing MPEG audio frames ...");
1702  break;
1703  default:
1704  updateStatus("Writing frames ...");
1705  }
1706  backupStream.seekg(streamOffset);
1707  CopyHelper<0x4000> copyHelper;
1708  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
1709  updatePercentage(100.0);
1710  } else {
1711  // just skip actual stream data
1712  outputStream.seekp(mediaDataSize, ios_base::cur);
1713  }
1714 
1715  // write ID3v1 tag
1716  if(m_id3v1Tag) {
1717  updateStatus("Writing ID3v1 tag ...");
1718  try {
1719  m_id3v1Tag->make(stream());
1720  } catch(const Failure &) {
1721  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1722  }
1723  }
1724 
1725  // handle streams
1726  if(rewriteRequired) {
1727  // report new size
1728  reportSizeChanged(outputStream.tellp());
1729  // "save as path" is now the regular path
1730  if(!saveFilePath().empty()) {
1732  m_saveFilePath.clear();
1733  }
1734  // stream is useless for further usage because it is write-only
1735  outputStream.close();
1736  } else {
1737  const auto newSize = static_cast<uint64>(outputStream.tellp());
1738  if(newSize < size()) {
1739  // file is smaller after the modification -> truncate
1740  // -> close stream before truncating
1741  outputStream.close();
1742  // -> truncate file
1743  if(truncate(path().c_str(), newSize) == 0) {
1744  reportSizeChanged(newSize);
1745  } else {
1746  addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
1747  }
1748  } else {
1749  // file is longer after the modification -> just report new size
1750  reportSizeChanged(newSize);
1751  }
1752  }
1753 
1754  } catch(...) {
1755  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, context);
1756  }
1757  }
1758 }
1759 
1760 }
bool areTagsSupported() const
Returns an indication whether this library supports the tag format of the current file...
MediaType
The MediaType enum specifies the type of media data (audio, video, text, ...).
Definition: mediaformat.h:13
void invalidateStatus()
Invalidates the current status.
ContainerFormat
Specifies the container format.
Definition: signature.h:17
void parseTracks()
Parses the tracks of the current file.
bool isAborted() const
Returns an indication whether the current operation should be aborted.
This exception is thrown when the an operation is invoked that has not been implemented yet...
Definition: exceptions.h:59
const NotificationList & notifications() const
Returns notifications for the current object.
AbstractContainer * container() const
Returns the container for the current file.
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
Implementation of Media::Tag for Vorbis comments.
Definition: vorbiscomment.h:15
NotificationType
Specifies the notification type.
Definition: notification.h:18
void mergeId3v2Tags()
Merges the assigned ID3v2 tags into a single ID3v2 tag.
ParsingStatus tagsParsingStatus() const
Returns an indication whether tag information has been parsed yet.
NotificationType worstNotificationType() const
Returns the worst notification type.
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
uint32 streamOffset() const
Returns the start offset of the actual FLAC frames.
Definition: flacstream.h:68
bool id3v2ToId3v1()
Converts the existing ID3v2 tags into an ID3v1 tag.
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
void parseTags()
Parses the tag(s) of the current file.
TAG_PARSER_EXPORT const char * containerFormatAbbreviation(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown, unsigned int version=0)
Returns the abbreviation of the container format as C-style string considering the specified media ty...
Definition: signature.cpp:240
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
const char * mimeType() const
Returns the MIME-type of the container format as C-style string.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:40
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, const std::string &context="making file")
STL namespace.
void parseChapters()
Parses the chapters of the current file.
const std::vector< std::unique_ptr< MatroskaTag > > & matroskaTags() const
Returns pointers to the assigned Matroska tags.
VorbisComment * createVorbisComment()
Creates a Vorbis comment for the current file.
static void makePadding(std::ostream &stream, uint32 size, bool isLast)
Writes padding of the specified size to the specified stream.
Definition: flacstream.cpp:257
void addNotification(const Notification &notification)
This protected method is meant to be called by the derived class to add a notification.
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string...
bool removeId3v1Tag()
Removes a possibly assigned ID3v1 tag from the current file.
bool hasAnyTag() const
Returns an indication whether a tag of any format is assigned.
NotificationList gatherRelatedNotifications() const
Returns the notifications of the current instance and all related objects (tracks, tags, container, ...).
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
uint64 size() const
Returns size of the current file in bytes.
bool removeId3v2Tag(Id3v2Tag *tag)
Removes an assigned ID3v2 tag from the current file.
void updatePercentage(double percentage)
This method is meant to be called by the derived class to report updated progress percentage only...
int insertFields(const FieldMapBasedTag< FieldType, Compare > &from, bool overwrite)
Inserts all fields from another tag of the same field type and compare function.
size_t minPadding() const
Returns the minimum padding to be written before the data blocks when applying changes.
ParsingStatus
The ParsingStatus enum specifies whether a certain part of the file (tracks, tags, ...) has been parsed yet and if what the parsing result is.
Definition: mediafileinfo.h:44
void applyChanges()
Applies assigned/changed tag information to the current file.
MediaFileInfo()
Constructs a new MediaFileInfo.
void open(bool readOnly=false)
Opens a std::fstream for the current file.
void parseAttachments()
Parses the attachments of the current file.
bool areAttachmentsSupported() const
Returns an indication whether this library supports attachment format of the current file...
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
#define MEDIAINFO_CPP_FORCE_FULL_PARSE
std::vector< AbstractChapter * > chapters() const
Returns all chapters assigned to the current file.
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
uint32 makeHeader(std::ostream &stream)
Writes the FLAC metadata header to the specified outputStream.
Definition: flacstream.cpp:180
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
Definition: mp4container.h:19
void close()
A possibly opened std::fstream will be closed.
static constexpr NotificationType worstNotificationType()
Returns the worst notification type.
Definition: notification.h:98
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:80
virtual unsigned int insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition: tag.cpp:127
ChronoUtilities::TimeSpan duration() const
Returns the overall duration of the file is known; otherwise returns a TimeSpan with zero ticks...
Implementation of Media::AbstractTrack for raw FLAC streams.
Definition: flacstream.h:14
NotificationType worstNotificationTypeIncludingRelatedObjects() const
Returns the worst notification type including related objects such as track, tag and container...
virtual void invalidated()
Reimplemented from BasicFileInfo::invalidated().
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
Definition: signature.cpp:102
Contains utility classes helping to read and write streams.
bool removeAllId3v2Tags()
Removes all assigned ID3v2 tags from the current file.
void setVersion(byte majorVersion, byte revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion. ...
Definition: id3v2tag.cpp:312
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:27
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
Implementation of Media::Tag for the MP4 container.
Definition: mp4tag.h:90
void parseHeader()
Parses the header if not parsed yet.
unsigned int insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
The Tag class is used to store, read and write tag information.
Definition: tag.h:98
std::vector< AbstractAttachment * > attachments() const
Returns all attachments assigned to the current file.
ParsingStatus attachmentsParsingStatus() const
Returns whether the attachments have been parsed yet.
std::vector< Tag * > tags() const
Returns all tags assigned to the current file.
bool id3v1ToId3v2()
Converts an existing ID3v1 tag into an ID3v2 tag.
const char * containerFormatAbbreviation() const
Returns the abbreviation of the container format as C-style string.
bool hasTracksOfType(Media::MediaType type) const
Returns an indication whether the current file has tracks of the specified type.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
bool removeVorbisComment()
Removes all assigned Vorbis comment from the current file.
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
bool haveRelatedObjectsNotifications() const
Returns an indication whether at least one related object (track, tag, container) has notifications...
void ensureTextValuesAreProperlyEncoded()
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition: id3v1tag.cpp:257
uint64 paddingSize() const
Returns the padding size.
const std::string & path() const
Returns the path of the current file.
Definition: basicfileinfo.h:98
bool hasId3v2Tag() const
Returns an indication whether an ID3v2 tag is assigned.
ParsingStatus tracksParsingStatus() const
Returns an indication whether tracks have been parsed yet.
Implementation of Media::Tag for ID3v2 tags.
Definition: id3v2tag.h:55
ParsingStatus containerParsingStatus() const
Returns an indication whether the container format has been parsed yet.
~MediaFileInfo()
Destroys the MediaFileInfo.
std::list< Notification > NotificationList
Definition: notification.h:39
void invalidateNotifications()
Invalidates the object&#39;s notifications.
void parseContainerFormat()
Parses the container format of the current file.
Implementation of Media::Tag for ID3v1 tags.
Definition: id3v1tag.h:9
VorbisComment * vorbisComment() const
Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
size_t maxPadding() const
Returns the maximum padding to be written before the data blocks when applying changes.
TagUsage
The TagUsage enum specifies the usage of a certain tag type.
Definition: mediafileinfo.h:33
void parseEverything()
Parses the container format, the tracks and the tag information of the current file.
Id3v2Tag * createId3v2Tag()
Creates an ID3v2 tag for the current file.
void removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
void clearParsingResults()
Clears all parsing results and assigned/created/changed information such as container format...
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
bool areTracksSupported() const
Returns an indication whether this library supports parsing the tracks information of the current fil...
Implementation of Media::AbstractContainer for OGG files.
Definition: oggcontainer.h:127
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:19
void updateStatus(const std::string &status)
This method is meant to be called by the derived class to report updated status information.
Mp4Tag * mp4Tag() const
Returns a pointer to the assigned MP4 tag or nullptr if none is assigned.
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:31
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
void removeAllTags()
Removes all assigned tags from the file.
virtual void invalidated()
This function is called when the BasicFileInfo gets invalidated.
bool createAppropriateTags(bool treatUnknownFilesAsMp3Files=false, TagUsage id3v1usage=TagUsage::KeepExisting, TagUsage id3v2usage=TagUsage::Always, bool id3InitOnCreate=false, bool id3TransferValuesOnRemoval=true, bool mergeMultipleSuccessiveId3v2Tags=true, bool keepExistingId3v2version=true, byte id3v2MajorVersion=3, const std::vector< TagTarget > &requiredTargets=std::vector< TagTarget >())
Ensures appropriate tags are created according the given specifications.
void addNotifications(const StatusProvider &from)
This protected method is meant to be called by the derived class to add all notifications from anothe...
The BasicFileInfo class provides basic file information such as file name, extension, directory and size for a specified file.
Definition: basicfileinfo.h:13
ContainerFormat containerFormat() const
Returns the container format of the current file.
bool areChaptersSupported() const
Returns an indication whether this library supports parsing the chapters of the current file...
std::size_t trackCount() const
Returns the number of tracks that could be parsed.
TAG_PARSER_EXPORT const char * version()
TAG_PARSER_EXPORT const char * containerMimeType(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown)
Returns the MIME-type of the container format as C-style string.
Definition: signature.cpp:432
bool hasId3v1Tag() const
Returns an indication whether an ID3v1 tag is assigned.
ParsingStatus chaptersParsingStatus() const
Returns whether the chapters have been parsed yet.