Tag Parser  6.2.1
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 then 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  if(m_singleTrack) {
781  res.push_back(m_singleTrack.get());
782  }
783  if(m_container) {
784  for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
785  res.push_back(m_container->track(i));
786  }
787  }
788  return res;
789 }
790 
800 {
802  if(m_singleTrack && m_singleTrack->mediaType() == type) {
803  return true;
804  } else if(m_container) {
805  for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
806  if(m_container->track(i)->mediaType() == type) {
807  return true;
808  }
809  }
810  }
811  }
812  return false;
813 }
814 
825 {
826  TimeSpan res;
827  if(m_container) {
828  res = m_container->duration();
829  } else {
830  for(const AbstractTrack *track : tracks()) {
831  if(track->duration() > res) {
832  res = track->duration();
833  }
834  }
835  }
836  return res;
837 }
838 
849 {
851  if(m_id3v1Tag) {
852  m_id3v1Tag.reset();
853  return true;
854  }
855  }
856  return false;
857 }
858 
875 {
877  return nullptr;
878  }
879  if(!m_id3v1Tag) {
880  m_id3v1Tag = make_unique<Id3v1Tag>();
881  }
882  return m_id3v1Tag.get();
883 }
884 
899 {
901  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
902  if(i->get() == tag) {
903  m_id3v2Tags.erase(i);
904  return true;
905  }
906  }
907  }
908  return false;
909 }
910 
920 {
921  if(tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
922  return false;
923  }
924  m_id3v2Tags.clear();
925  return true;
926 }
927 
944 {
945  if(m_id3v2Tags.empty()) {
946  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
947  }
948  return m_id3v2Tags.front().get();
949 }
950 
964 {
965  if(tag) {
966  if(m_container) {
967  m_container->removeTag(tag);
968  }
969  if(m_id3v1Tag.get() == tag) {
970  m_id3v1Tag.reset();
971  }
972  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
973  if(i->get() == tag) {
974  m_id3v2Tags.erase(i);
975  break;
976  }
977  }
978  }
979 }
980 
987 {
988  if(m_container) {
989  m_container->removeAllTags();
990  }
991  if(m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
992  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
993  }
994  m_id3v1Tag.reset();
995  m_id3v2Tags.clear();
996 }
997 
1002 {
1003  if(m_container && m_container->chapterCount()) {
1004  return true;
1005  }
1006  switch(m_containerFormat) {
1008  case ContainerFormat::Webm:
1009  return true;
1010  default:
1011  return false;
1012  }
1013 }
1014 
1019 {
1020  if(m_container && m_container->attachmentCount()) {
1021  return true;
1022  }
1023  switch(m_containerFormat) {
1025  case ContainerFormat::Webm:
1026  return true;
1027  default:
1028  return false;
1029  }
1030 }
1031 
1036 {
1037  if(trackCount()) {
1038  return true;
1039  }
1040  switch(m_containerFormat) {
1041  case ContainerFormat::Mp4:
1044  case ContainerFormat::Ogg:
1046  case ContainerFormat::Webm:
1047  return true;
1048  default:
1049  return false;
1050  }
1051 }
1052 
1057 {
1058  switch(m_containerFormat) {
1059  case ContainerFormat::Adts:
1060  case ContainerFormat::Flac:
1063  case ContainerFormat::Mp4:
1064  case ContainerFormat::Ogg:
1065  case ContainerFormat::Webm:
1066  // these container formats are supported
1067  return true;
1068  default:
1069  // the container format is unsupported
1070  // -> an ID3 tag might be already present, in this case the tags are considered supported
1071  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1072  }
1073 }
1074 
1081 {
1082  // simply return the first tag here since MP4 files never contain multiple tags
1083  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;
1084 }
1085 
1092 const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
1093 {
1094  // matroska files might contain multiple tags (targeting different scopes)
1095  if(m_containerFormat == ContainerFormat::Matroska && m_container) {
1096  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1097  } else {
1098  static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1099  return empty;
1100  }
1101 }
1102 
1109 {
1110  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1111  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1112  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack
1113  ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()
1114  : nullptr);
1115 }
1116 
1122 vector<AbstractChapter *> MediaFileInfo::chapters() const
1123 {
1124  vector<AbstractChapter *> res;
1125  if(m_container) {
1126  size_t count = m_container->chapterCount();
1127  res.reserve(count);
1128  for(size_t i = 0; i != count; ++i) {
1129  res.push_back(m_container->chapter(i));
1130  }
1131  }
1132  return res;
1133 }
1134 
1140 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1141 {
1142  vector<AbstractAttachment *> res;
1143  if(m_container) {
1144  size_t count = m_container->attachmentCount();
1145  res.reserve(count);
1146  for(size_t i = 0; i < count; ++i) {
1147  res.push_back(m_container->attachment(i));
1148  }
1149  }
1150  return res;
1151 }
1152 
1158 {
1159  if(m_container) {
1160  if(m_container->hasNotifications()) {
1161  return true;
1162  }
1163  }
1164  for(const auto *track : tracks()) {
1165  if(track->hasNotifications()) {
1166  return true;
1167  }
1168  }
1169  for(const auto *tag : tags()) {
1170  if(tag->hasNotifications()) {
1171  return true;
1172  }
1173  }
1174  for(const auto *chapter : chapters()) {
1175  if(chapter->hasNotifications()) {
1176  return true;
1177  }
1178  }
1179  return false;
1180 }
1181 
1187 {
1189  if(type == Notification::worstNotificationType()) {
1190  return type;
1191  }
1192  if(m_container) {
1193  type |= m_container->worstNotificationType();
1194  }
1195  if(type == Notification::worstNotificationType()) {
1196  return type;
1197  }
1198  for(const auto *track : tracks()) {
1199  type |= track->worstNotificationType();
1200  if(type == Notification::worstNotificationType()) {
1201  return type;
1202  }
1203  }
1204  for(const auto *tag : tags()) {
1205  type |= tag->worstNotificationType();
1206  if(type == Notification::worstNotificationType()) {
1207  return type;
1208  }
1209  }
1210  for(const auto *chapter : chapters()) {
1211  type |= chapter->worstNotificationType();
1212  if(type == Notification::worstNotificationType()) {
1213  return type;
1214  }
1215  }
1216  return type;
1217 }
1218 
1225 {
1226  notifications.insert(notifications.end(), this->notifications().cbegin(), this->notifications().cend());
1227  if(m_container) {
1228  notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
1229  }
1230  for(const auto *track : tracks()) {
1231  notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1232  }
1233  for(const auto *tag : tags()) {
1234  notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1235  }
1236  for(const auto *chapter : chapters()) {
1237  notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1238  }
1239  for(const auto *attachment : attachments()) {
1240  notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1241  }
1242 }
1243 
1249 {
1251  gatherRelatedNotifications(notifications);
1252  return notifications;
1253 }
1254 
1265 {
1266  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1267  m_containerFormat = ContainerFormat::Unknown;
1268  m_containerOffset = 0;
1269  m_paddingSize = 0;
1270  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1271  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1272  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1273  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1274  m_id3v1Tag.reset();
1275  m_id3v2Tags.clear();
1276  m_actualId3v2TagOffsets.clear();
1277  m_actualExistingId3v1Tag = false;
1278  m_container.reset();
1279  m_singleTrack.reset();
1280 }
1281 
1299 {
1300  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1301  if(begin != end) {
1302  Id3v2Tag &first = **begin;
1303  auto isecond = begin + 1;
1304  if(isecond != end) {
1305  for(auto i = isecond; i != end; ++i) {
1306  first.insertFields(**i, false);
1307  }
1308  m_id3v2Tags.erase(isecond, end - 1);
1309  }
1310  }
1311 }
1312 
1325 {
1326  if(!areTagsSupported() || !m_container) {
1327  return createAppropriateTags(false, TagUsage::Never, TagUsage::Always, true, true, 3);
1328  } else {
1329  return false;
1330  }
1331 }
1332 
1345 {
1346  if(!areTagsSupported() || !m_container) {
1347  return createAppropriateTags(false, TagUsage::Always, TagUsage::Never, true, true, 3);
1348  } else {
1349  return false;
1350  }
1351 }
1352 
1368 {
1369  switch(m_containerFormat) {
1370  case ContainerFormat::Ogg:
1371  if(m_container) {
1372  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1373  }
1374  break;
1375  case ContainerFormat::Flac:
1376  if(m_singleTrack) {
1377  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1378  }
1379  break;
1380  default:
1381  ;
1382  }
1383  return nullptr;
1384 }
1385 
1396 {
1397  switch(m_containerFormat) {
1398  case ContainerFormat::Ogg:
1399  if(m_container) {
1400  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1401  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1402  return hadTags;
1403  }
1404  break;
1405  case ContainerFormat::Flac:
1406  if(m_singleTrack) {
1407  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1408  }
1409  break;
1410  default:
1411  ;
1412  }
1413  return false;
1414 }
1415 
1424 void MediaFileInfo::tags(vector<Tag *> &tags) const
1425 {
1426  if(hasId3v1Tag()) {
1427  tags.push_back(m_id3v1Tag.get());
1428  }
1429  for(const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1430  tags.push_back(tag.get());
1431  }
1432  if(m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1433  if(auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1434  tags.push_back(vorbisComment);
1435  }
1436  }
1437  if(m_container) {
1438  for(size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1439  tags.push_back(m_container->tag(i));
1440  }
1441  }
1442 }
1443 
1448 {
1449  return hasId3v1Tag()
1450  || hasId3v2Tag()
1451  || (m_container && m_container->tagCount())
1452  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1453 }
1454 
1461 vector<Tag *> MediaFileInfo::tags() const
1462 {
1463  vector<Tag *> res;
1464  tags(res);
1465  return res;
1466 }
1467 
1472 {
1474  invalidateStatus();
1477 }
1478 
1482 void MediaFileInfo::makeMp3File()
1483 {
1484  static const string context("making MP3/FLAC file");
1485  // there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
1486  if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty() && m_containerFormat != ContainerFormat::Flac) {
1487  if(m_actualExistingId3v1Tag) {
1488  // there is currently an ID3v1 tag at the end of the file
1489  if(m_id3v1Tag) {
1490  // the file shall still have an ID3v1 tag
1491  updateStatus("Updating ID3v1 tag ...");
1492  // ensure the file is still open / not readonly
1493  open();
1494  stream().seekp(-128, ios_base::end);
1495  try {
1496  m_id3v1Tag->make(stream());
1497  } catch(const Failure &) {
1498  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1499  }
1500  } else {
1501  // the currently existing ID3v1 tag shall be removed
1502  updateStatus("Removing ID3v1 tag ...");
1503  stream().close();
1504  if(truncate(path().c_str(), size() - 128) == 0) {
1505  reportSizeChanged(size() - 128);
1506  } else {
1507  addNotification(NotificationType::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1508  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1509  }
1510  }
1511 
1512  } else {
1513  // there is currently no ID3v1 tag at the end of the file
1514  if(m_id3v1Tag) {
1515  updateStatus("Adding ID3v1 tag ...");
1516  // ensure the file is still open / not readonly
1517  open();
1518  stream().seekp(0, ios_base::end);
1519  try {
1520  m_id3v1Tag->make(stream());
1521  } catch(const Failure &) {
1522  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1523  }
1524  } else {
1525  addNotification(NotificationType::Information, "Nothing to be changed.", context);
1526  }
1527  }
1528 
1529  } else {
1530  // ID3v2 needs to be modified
1531  updateStatus("Updating ID3v2 tags ...");
1532 
1533  // prepare ID3v2 tags
1534  vector<Id3v2TagMaker> makers;
1535  makers.reserve(m_id3v2Tags.size());
1536  uint32 tagsSize = 0;
1537  for(auto &tag : m_id3v2Tags) {
1538  try {
1539  makers.emplace_back(tag->prepareMaking());
1540  tagsSize += makers.back().requiredSize();
1541  } catch(const Failure &) {
1542  // nothing to do: notifications added anyways
1543  }
1544  addNotifications(*tag);
1545  }
1546 
1547  // check whether it is a raw FLAC stream
1548  FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1549  uint32 streamOffset; // where the actual stream starts
1550  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1551  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1552  uint32 startOfLastMetaDataBlock;
1553 
1554  if(flacStream) {
1555  // if it is a raw FLAC stream, make FLAC metadata
1556  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData);
1557  tagsSize += flacMetaData.tellp();
1558  streamOffset = flacStream->streamOffset();
1559  } else {
1560  // make no further metadata, just use the container offset as stream offset
1561  streamOffset = static_cast<uint32>(m_containerOffset);
1562  }
1563 
1564  // check whether rewrite is required
1565  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1566  uint32 padding = 0;
1567  if(!rewriteRequired) {
1568  // rewriting is not forced and new tag is not too big for available space
1569  // -> calculate new padding
1570  padding = streamOffset - tagsSize;
1571  // -> check whether the new padding matches specifications
1572  if(padding < minPadding() || padding > maxPadding()) {
1573  rewriteRequired = true;
1574  }
1575  }
1576  if(makers.empty() && !flacStream) {
1577  // an ID3v2 tag is not written and it is not a FLAC stream
1578  // -> can't include padding
1579  if(padding) {
1580  // but padding would be present -> need to rewrite
1581  padding = 0; // can't write the preferred padding despite rewriting
1582  rewriteRequired = true;
1583  }
1584  } else if(rewriteRequired) {
1585  // rewriting is forced or new ID3v2 tag is too big for available space
1586  // -> use preferred padding when rewriting anyways
1587  padding = preferredPadding();
1588  } else if(makers.empty() && flacStream && padding && padding < 4) {
1589  // no ID3v2 tag -> must include padding in FLAC stream
1590  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1591  padding = preferredPadding();
1592  rewriteRequired = true;
1593  }
1594  if(rewriteRequired && flacStream && makers.empty() && padding) {
1595  // the first 4 byte of FLAC padding actually don't count because these
1596  // can not be used for additional meta data
1597  padding += 4;
1598  }
1599  updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1600 
1601  // setup stream(s) for writing
1602  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1603  string backupPath;
1604  NativeFileStream &outputStream = stream();
1605  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1606 
1607  if(rewriteRequired) {
1608  if(m_saveFilePath.empty()) {
1609  // move current file to temp dir and reopen it as backupStream, recreate original file
1610  try {
1611  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1612  // recreate original file, define buffer variables
1613  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1614  } catch(...) {
1615  const char *what = catchIoFailure();
1616  addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1617  throwIoFailure(what);
1618  }
1619  } else {
1620  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1621  try {
1622  close();
1623  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1624  backupStream.open(path(), ios_base::in | ios_base::binary);
1625  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1626  } catch(...) {
1627  const char *what = catchIoFailure();
1628  addNotification(NotificationType::Critical, "Opening streams to write output file failed.", context);
1629  throwIoFailure(what);
1630  }
1631  }
1632 
1633  } else { // !rewriteRequired
1634  // reopen original file to ensure it is opened for writing
1635  try {
1636  close();
1637  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1638  } catch(...) {
1639  const char *what = catchIoFailure();
1640  addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
1641  throwIoFailure(what);
1642  }
1643  }
1644 
1645  // start actual writing
1646  try {
1647  if(!makers.empty()) {
1648  // write ID3v2 tags
1649  updateStatus("Writing ID3v2 tag ...");
1650  for(auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1651  i->make(outputStream, 0);
1652  }
1653  // include padding into the last ID3v2 tag
1654  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1655  }
1656 
1657  if(flacStream) {
1658  if(padding && startOfLastMetaDataBlock) {
1659  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1660  flacMetaData.seekg(startOfLastMetaDataBlock);
1661  flacMetaData.seekp(startOfLastMetaDataBlock);
1662  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1663  flacMetaData.seekg(0);
1664  }
1665 
1666  // write FLAC metadata
1667  outputStream << flacMetaData.rdbuf();
1668 
1669  // write padding
1670  if(padding) {
1671  flacStream->makePadding(outputStream, padding, true);
1672  }
1673  }
1674 
1675  if(makers.empty() && !flacStream){
1676  // just write padding (however, padding should be set to 0 in this case?)
1677  for(; padding; --padding) {
1678  outputStream.put(0);
1679  }
1680  }
1681 
1682  // copy / skip actual stream data
1683  // -> determine media data size
1684  uint64 mediaDataSize = size() - streamOffset;
1685  if(m_actualExistingId3v1Tag) {
1686  mediaDataSize -= 128;
1687  }
1688 
1689  if(rewriteRequired) {
1690  // copy data from original file
1691  switch(m_containerFormat) {
1693  updateStatus("Writing MPEG audio frames ...");
1694  break;
1695  default:
1696  updateStatus("Writing frames ...");
1697  }
1698  backupStream.seekg(streamOffset);
1699  CopyHelper<0x4000> copyHelper;
1700  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
1701  updatePercentage(100.0);
1702  } else {
1703  // just skip actual stream data
1704  outputStream.seekp(mediaDataSize, ios_base::cur);
1705  }
1706 
1707  // write ID3v1 tag
1708  if(m_id3v1Tag) {
1709  updateStatus("Writing ID3v1 tag ...");
1710  try {
1711  m_id3v1Tag->make(stream());
1712  } catch(const Failure &) {
1713  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1714  }
1715  }
1716 
1717  // handle streams
1718  if(rewriteRequired) {
1719  // report new size
1720  reportSizeChanged(outputStream.tellp());
1721  // "save as path" is now the regular path
1722  if(!saveFilePath().empty()) {
1724  m_saveFilePath.clear();
1725  }
1726  // stream is useless for further usage because it is write-only
1727  outputStream.close();
1728  } else {
1729  const auto newSize = static_cast<uint64>(outputStream.tellp());
1730  if(newSize < size()) {
1731  // file is smaller after the modification -> truncate
1732  // -> close stream before truncating
1733  outputStream.close();
1734  // -> truncate file
1735  if(truncate(path().c_str(), newSize) == 0) {
1736  reportSizeChanged(newSize);
1737  } else {
1738  addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
1739  }
1740  } else {
1741  // file is longer after the modification -> just report new size
1742  reportSizeChanged(newSize);
1743  }
1744  }
1745 
1746  } catch(...) {
1747  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, context);
1748  }
1749  }
1750 }
1751 
1752 }
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.