Tag Parser  6.5.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 
104 MediaFileInfo::MediaFileInfo(const string &path) :
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  }
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);
248  break;
249  } case ContainerFormat::Ogg:
250  // Ogg is handled by 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()) {
557  break;
558  default:
559  return false;
560  }
561  }
562  // create ID3 tags according to id3v2usage and id3v2usage
563  if(id3v1usage == TagUsage::Always) {
564  // always create ID3v1 tag -> ensure there is one
565  if(!id3v1Tag()) {
567  if(id3InitOnCreate) {
568  for(const auto &id3v2Tag : id3v2Tags()) {
569  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
570  id3v1Tag->insertValues(*id3v2Tag, true);
571  // ID3v1 does not support all text encodings which might be used in ID3v2
573  }
574  }
575  }
576  }
577  if(id3v2usage == TagUsage::Always) {
578  // always create ID3v2 tag -> ensure there is one and set version
579  if(!hasId3v2Tag()) {
580  Id3v2Tag *id3v2Tag = createId3v2Tag();
581  id3v2Tag->setVersion(id3v2MajorVersion, 0);
582  if(id3InitOnCreate && id3v1Tag()) {
583  id3v2Tag->insertValues(*id3v1Tag(), true);
584  }
585  }
586  }
587  }
588 
589  if(mergeMultipleSuccessiveId3v2Tags) {
590  mergeId3v2Tags();
591  }
592  // remove ID3 tags according to id3v1usage and id3v2usage
593  if(id3v1usage == TagUsage::Never) {
594  if(hasId3v1Tag()) {
595  // transfer tags to ID3v2 tag before removing
596  if(id3TransferValuesOnRemoval && hasId3v2Tag()) {
597  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
598  }
599  removeId3v1Tag();
600  }
601  }
602  if(id3v2usage == TagUsage::Never) {
603  if(id3TransferValuesOnRemoval && hasId3v1Tag()) {
604  // transfer tags to ID3v1 tag before removing
605  for(const auto &tag : id3v2Tags()) {
606  id3v1Tag()->insertValues(*tag, false);
607  }
608  }
610  } else if(!keepExistingId3v2version) {
611  // set version of ID3v2 tag according user preferences
612  for(const auto &tag : id3v2Tags()) {
613  tag->setVersion(id3v2MajorVersion, 0);
614  }
615  }
616  }
617  if(targetsRequired && !targetsSupported) {
618  addNotification(NotificationType::Information, "The container/tags do not support targets. The specified targets are ignored.", "creating tags");
619  }
620  return true;
621 }
622 
623 
645 {
646  static 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  } catch(...) {
682  // since the file might be messed up, invalidate the parsing results
684  throw;
685  }
686  } else { // implementation if no container object is present
687  // assume the file is a MP3 file
688  try {
689  makeMp3File();
690  } catch(...) {
691  // since the file might be messed up, invalidate the parsing results
693  throw;
694  }
695  }
697 }
698 
712 {
713  MediaType mediaType = MediaType::Unknown;
714  unsigned int version = 0;
715  switch(m_containerFormat) {
716  case ContainerFormat::Ogg: {
717  // check for video track or whether only Opus or Speex tracks are present
718  const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
719  if(tracks.empty()) {
720  break;
721  }
722  bool onlyOpus = true, onlySpeex = true;
723  for(const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
724  if(track->mediaType() == MediaType::Video) {
725  mediaType = MediaType::Video;
726  }
727  if(track->format().general != GeneralMediaFormat::Opus) {
728  onlyOpus = false;
729  }
730  if(track->format().general != GeneralMediaFormat::Speex) {
731  onlySpeex = false;
732  }
733  }
734  if(onlyOpus) {
735  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
736  } else if(onlySpeex) {
737  version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
738  }
739  break;
743  break;
745  if(m_singleTrack) {
746  version = m_singleTrack->format().sub;
747  }
748  break;
749  default:
750  ;
751  }
752  return Media::containerFormatAbbreviation(m_containerFormat, mediaType, version);
753 }
754 
765 const char *MediaFileInfo::mimeType() const
766 {
767  MediaType mediaType;
768  switch(m_containerFormat) {
773  break;
774  default:
775  mediaType = MediaType::Unknown;
776  }
777  return Media::containerMimeType(m_containerFormat, mediaType);
778 }
779 
792 vector<AbstractTrack *> MediaFileInfo::tracks() const
793 {
794  vector<AbstractTrack *> res;
795  size_t trackCount = 0;
796  size_t containerTrackCount = 0;
797  if(m_singleTrack) {
798  trackCount = 1;
799  }
800  if(m_container) {
801  trackCount += (containerTrackCount = m_container->trackCount());
802  }
803  res.reserve(trackCount);
804 
805  if(m_singleTrack) {
806  res.push_back(m_singleTrack.get());
807  }
808  for(size_t i = 0; i != containerTrackCount; ++i) {
809  res.push_back(m_container->track(i));
810  }
811  return res;
812 }
813 
823 {
825  return false;
826  }
827  if(m_singleTrack && m_singleTrack->mediaType() == type) {
828  return true;
829  } else if(m_container) {
830  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
831  if(m_container->track(i)->mediaType() == type) {
832  return true;
833  }
834  }
835  }
836  return false;
837 }
838 
849 {
850  if(m_container) {
851  return m_container->duration();
852  } else if(m_singleTrack) {
853  return m_singleTrack->duration();
854  }
855  return TimeSpan();
856 }
857 
868 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
869 {
870  unordered_set<string> res;
871  if(m_container) {
872  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
873  const AbstractTrack *track = m_container->track(i);
874  if((type == MediaType::Unknown || track->mediaType() == type) && !track->language().empty() && track->language() != "und") {
875  res.emplace(track->language());
876  }
877  }
878  } else if(m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty() && m_singleTrack->language() != "und") {
879  res.emplace(m_singleTrack->language());
880  }
881  return res;
882 }
883 
896 {
897  if(m_container) {
898  const size_t trackCount = m_container->trackCount();
899  vector<string> parts;
900  parts.reserve(trackCount);
901  for(size_t i = 0; i != trackCount; ++i) {
902  const string description(m_container->track(i)->description());
903  if(!description.empty()) {
904  parts.emplace_back(move(description));
905  }
906  }
907  return joinStrings(parts, " / ");
908  } else if(m_singleTrack) {
909  return m_singleTrack->description();
910  }
911  return string();
912 }
913 
924 {
926  return false;
927  }
928  if(m_id3v1Tag) {
929  m_id3v1Tag.reset();
930  return true;
931  }
932  return false;
933 }
934 
951 {
953  return nullptr;
954  }
955  if(!m_id3v1Tag) {
956  m_id3v1Tag = make_unique<Id3v1Tag>();
957  }
958  return m_id3v1Tag.get();
959 }
960 
975 {
977  return false;
978  }
979  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
980  if(i->get() == tag) {
981  m_id3v2Tags.erase(i);
982  return true;
983  }
984  }
985  return false;
986 }
987 
997 {
998  if(tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
999  return false;
1000  }
1001  m_id3v2Tags.clear();
1002  return true;
1003 }
1004 
1021 {
1022  if(m_id3v2Tags.empty()) {
1023  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1024  }
1025  return m_id3v2Tags.front().get();
1026 }
1027 
1041 {
1042  if(!tag) {
1043  return;
1044  }
1045  if(m_container) {
1046  m_container->removeTag(tag);
1047  }
1048  if(m_id3v1Tag.get() == tag) {
1049  m_id3v1Tag.reset();
1050  }
1051  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1052  if(i->get() == tag) {
1053  m_id3v2Tags.erase(i);
1054  break;
1055  }
1056  }
1057 }
1058 
1065 {
1066  if(m_container) {
1067  m_container->removeAllTags();
1068  }
1069  if(m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1070  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1071  }
1072  m_id3v1Tag.reset();
1073  m_id3v2Tags.clear();
1074 }
1075 
1080 {
1081  if(m_container && m_container->chapterCount()) {
1082  return true;
1083  }
1084  switch(m_containerFormat) {
1086  case ContainerFormat::Webm:
1087  return true;
1088  default:
1089  return false;
1090  }
1091 }
1092 
1097 {
1098  if(m_container && m_container->attachmentCount()) {
1099  return true;
1100  }
1101  switch(m_containerFormat) {
1103  case ContainerFormat::Webm:
1104  return true;
1105  default:
1106  return false;
1107  }
1108 }
1109 
1114 {
1115  if(trackCount()) {
1116  return true;
1117  }
1118  switch(m_containerFormat) {
1119  case ContainerFormat::Mp4:
1122  case ContainerFormat::Ogg:
1124  case ContainerFormat::Webm:
1125  return true;
1126  default:
1127  return false;
1128  }
1129 }
1130 
1135 {
1136  switch(m_containerFormat) {
1137  case ContainerFormat::Adts:
1138  case ContainerFormat::Flac:
1141  case ContainerFormat::Mp4:
1142  case ContainerFormat::Ogg:
1144  case ContainerFormat::Webm:
1145  // these container formats are supported
1146  return true;
1147  default:
1148  // the container format is unsupported
1149  // -> an ID3 tag might be already present, in this case the tags are considered supported
1150  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1151  }
1152 }
1153 
1160 {
1161  // simply return the first tag here since MP4 files never contain multiple tags
1162  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;
1163 }
1164 
1171 const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
1172 {
1173  // matroska files might contain multiple tags (targeting different scopes)
1174  if(m_containerFormat == ContainerFormat::Matroska && m_container) {
1175  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1176  } else {
1177  static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1178  return empty;
1179  }
1180 }
1181 
1188 {
1189  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1190  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1191  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack
1192  ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()
1193  : nullptr);
1194 }
1195 
1201 vector<AbstractChapter *> MediaFileInfo::chapters() const
1202 {
1203  vector<AbstractChapter *> res;
1204  if(m_container) {
1205  const size_t count = m_container->chapterCount();
1206  res.reserve(count);
1207  for(size_t i = 0; i != count; ++i) {
1208  res.push_back(m_container->chapter(i));
1209  }
1210  }
1211  return res;
1212 }
1213 
1219 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1220 {
1221  vector<AbstractAttachment *> res;
1222  if(m_container) {
1223  const size_t count = m_container->attachmentCount();
1224  res.reserve(count);
1225  for(size_t i = 0; i != count; ++i) {
1226  res.push_back(m_container->attachment(i));
1227  }
1228  }
1229  return res;
1230 }
1231 
1237 {
1238  if(m_container && m_container->hasNotifications()) {
1239  return true;
1240  }
1241  for(const auto *track : tracks()) {
1242  if(track->hasNotifications()) {
1243  return true;
1244  }
1245  }
1246  for(const auto *tag : tags()) {
1247  if(tag->hasNotifications()) {
1248  return true;
1249  }
1250  }
1251  for(const auto *chapter : chapters()) {
1252  if(chapter->hasNotifications()) {
1253  return true;
1254  }
1255  }
1256  return false;
1257 }
1258 
1264 {
1266  if(type == Notification::worstNotificationType()) {
1267  return type;
1268  }
1269  if(m_container) {
1270  type |= m_container->worstNotificationType();
1271  }
1272  if(type == Notification::worstNotificationType()) {
1273  return type;
1274  }
1275  for(const auto *track : tracks()) {
1276  type |= track->worstNotificationType();
1277  if(type == Notification::worstNotificationType()) {
1278  return type;
1279  }
1280  }
1281  for(const auto *tag : tags()) {
1282  type |= tag->worstNotificationType();
1283  if(type == Notification::worstNotificationType()) {
1284  return type;
1285  }
1286  }
1287  for(const auto *chapter : chapters()) {
1288  type |= chapter->worstNotificationType();
1289  if(type == Notification::worstNotificationType()) {
1290  return type;
1291  }
1292  }
1293  return type;
1294 }
1295 
1302 {
1303  notifications.insert(notifications.end(), this->notifications().cbegin(), this->notifications().cend());
1304  if(m_container) {
1305  // prevent duplicates which might be present when validating element structure
1306  switch(m_containerFormat) {
1307  case ContainerFormat::Ebml:
1309  // those files are only validated when a full parse is forced
1310  if(!m_forceFullParse) {
1311  notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
1312  break;
1313  }
1314  FALLTHROUGH;
1315  case ContainerFormat::Mp4:
1317  // those files are always validated
1318  for(const Notification &notification : m_container->notifications()) {
1319  if(find(notifications.cbegin(), notifications.cend(), notification) == notifications.cend()) {
1320  notifications.emplace_back(notification);
1321  }
1322  }
1323  break;
1324  default:
1325  notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());;
1326  }
1327  }
1328  for(const auto *track : tracks()) {
1329  notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1330  }
1331  for(const auto *tag : tags()) {
1332  notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1333  }
1334  for(const auto *chapter : chapters()) {
1335  notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1336  }
1337  for(const auto *attachment : attachments()) {
1338  notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1339  }
1340 }
1341 
1347 {
1350  return notifications;
1351 }
1352 
1366 {
1367  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1368  m_containerFormat = ContainerFormat::Unknown;
1369  m_containerOffset = 0;
1370  m_paddingSize = 0;
1371  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1372  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1373  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1374  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1375  if(m_id3v1Tag) {
1376  transferNotifications(*m_id3v1Tag);
1377  m_id3v1Tag.reset();
1378  }
1379  for(auto &id3v2Tag : m_id3v2Tags) {
1380  transferNotifications(*id3v2Tag);
1381  }
1382  m_id3v2Tags.clear();
1383  m_actualId3v2TagOffsets.clear();
1384  m_actualExistingId3v1Tag = false;
1385  if(m_container) {
1386  transferNotifications(*m_container);
1387  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
1388  transferNotifications(*m_container->track(i));
1389  }
1390  for(size_t i = 0, count = m_container->tagCount(); i != count; ++i) {
1391  transferNotifications(*m_container->tag(i));
1392  }
1393  for(size_t i = 0, count = m_container->chapterCount(); i != count; ++i) {
1394  transferNotifications(*m_container->chapter(i));
1395  }
1396  for(size_t i = 0, count = m_container->attachmentCount(); i != count; ++i) {
1397  transferNotifications(*m_container->attachment(i));
1398  }
1399  m_container.reset();
1400  }
1401  if(m_singleTrack) {
1402  transferNotifications(*m_singleTrack);
1403  m_singleTrack.reset();
1404  }
1405 }
1406 
1424 {
1425  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1426  if(begin == end) {
1427  return;
1428  }
1429  Id3v2Tag &first = **begin;
1430  auto isecond = begin + 1;
1431  if(isecond == end) {
1432  return;
1433  }
1434  for(auto i = isecond; i != end; ++i) {
1435  first.insertFields(**i, false);
1436  }
1437  m_id3v2Tags.erase(isecond, end - 1);
1438 }
1439 
1451 {
1453  return createAppropriateTags(false, TagUsage::Never, TagUsage::Always, true, true, 3);
1454  } else {
1455  return false;
1456  }
1457 }
1458 
1470 {
1472  return createAppropriateTags(false, TagUsage::Always, TagUsage::Never, true, true, 3);
1473  } else {
1474  return false;
1475  }
1476 }
1477 
1493 {
1494  switch(m_containerFormat) {
1495  case ContainerFormat::Ogg:
1496  if(m_container) {
1497  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1498  }
1499  break;
1500  case ContainerFormat::Flac:
1501  if(m_singleTrack) {
1502  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1503  }
1504  break;
1505  default:
1506  ;
1507  }
1508  return nullptr;
1509 }
1510 
1521 {
1522  switch(m_containerFormat) {
1523  case ContainerFormat::Ogg:
1524  if(m_container) {
1525  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1526  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1527  return hadTags;
1528  }
1529  break;
1530  case ContainerFormat::Flac:
1531  if(m_singleTrack) {
1532  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1533  }
1534  break;
1535  default:
1536  ;
1537  }
1538  return false;
1539 }
1540 
1549 void MediaFileInfo::tags(vector<Tag *> &tags) const
1550 {
1551  if(hasId3v1Tag()) {
1552  tags.push_back(m_id3v1Tag.get());
1553  }
1554  for(const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1555  tags.push_back(tag.get());
1556  }
1557  if(m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1558  if(auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1559  tags.push_back(vorbisComment);
1560  }
1561  }
1562  if(m_container) {
1563  for(size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1564  tags.push_back(m_container->tag(i));
1565  }
1566  }
1567 }
1568 
1573 {
1574  return hasId3v1Tag()
1575  || hasId3v2Tag()
1576  || (m_container && m_container->tagCount())
1577  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1578 }
1579 
1586 vector<Tag *> MediaFileInfo::tags() const
1587 {
1588  vector<Tag *> res;
1589  tags(res);
1590  return res;
1591 }
1592 
1597 {
1599  invalidateStatus();
1602 }
1603 
1607 void MediaFileInfo::makeMp3File()
1608 {
1609  static const string context("making MP3/FLAC file");
1610  // there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
1611  if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty() && m_containerFormat != ContainerFormat::Flac) {
1612  if(m_actualExistingId3v1Tag) {
1613  // there is currently an ID3v1 tag at the end of the file
1614  if(m_id3v1Tag) {
1615  // the file shall still have an ID3v1 tag
1616  updateStatus("Updating ID3v1 tag ...");
1617  // ensure the file is still open / not readonly
1618  open();
1619  stream().seekp(-128, ios_base::end);
1620  try {
1621  m_id3v1Tag->make(stream());
1622  } catch(const Failure &) {
1623  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1624  }
1625  } else {
1626  // the currently existing ID3v1 tag shall be removed
1627  updateStatus("Removing ID3v1 tag ...");
1628  stream().close();
1629  if(truncate(path().c_str(), size() - 128) == 0) {
1630  reportSizeChanged(size() - 128);
1631  } else {
1632  addNotification(NotificationType::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1633  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1634  }
1635  }
1636 
1637  } else {
1638  // there is currently no ID3v1 tag at the end of the file
1639  if(m_id3v1Tag) {
1640  updateStatus("Adding ID3v1 tag ...");
1641  // ensure the file is still open / not readonly
1642  open();
1643  stream().seekp(0, ios_base::end);
1644  try {
1645  m_id3v1Tag->make(stream());
1646  } catch(const Failure &) {
1647  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1648  }
1649  } else {
1650  addNotification(NotificationType::Information, "Nothing to be changed.", context);
1651  }
1652  }
1653 
1654  } else {
1655  // ID3v2 needs to be modified
1656  updateStatus("Updating ID3v2 tags ...");
1657 
1658  // prepare ID3v2 tags
1659  vector<Id3v2TagMaker> makers;
1660  makers.reserve(m_id3v2Tags.size());
1661  uint32 tagsSize = 0;
1662  for(auto &tag : m_id3v2Tags) {
1663  try {
1664  makers.emplace_back(tag->prepareMaking());
1665  tagsSize += makers.back().requiredSize();
1666  } catch(const Failure &) {
1667  // nothing to do: notifications added anyways
1668  }
1669  addNotifications(*tag);
1670  }
1671 
1672  // check whether it is a raw FLAC stream
1673  FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1674  uint32 streamOffset; // where the actual stream starts
1675  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1676  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1677  uint32 startOfLastMetaDataBlock;
1678 
1679  if(flacStream) {
1680  // if it is a raw FLAC stream, make FLAC metadata
1681  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData);
1682  tagsSize += flacMetaData.tellp();
1683  streamOffset = flacStream->streamOffset();
1684  } else {
1685  // make no further metadata, just use the container offset as stream offset
1686  streamOffset = static_cast<uint32>(m_containerOffset);
1687  }
1688 
1689  // check whether rewrite is required
1690  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1691  uint32 padding = 0;
1692  if(!rewriteRequired) {
1693  // rewriting is not forced and new tag is not too big for available space
1694  // -> calculate new padding
1695  padding = streamOffset - tagsSize;
1696  // -> check whether the new padding matches specifications
1697  if(padding < minPadding() || padding > maxPadding()) {
1698  rewriteRequired = true;
1699  }
1700  }
1701  if(makers.empty() && !flacStream) {
1702  // an ID3v2 tag is not written and it is not a FLAC stream
1703  // -> can't include padding
1704  if(padding) {
1705  // but padding would be present -> need to rewrite
1706  padding = 0; // can't write the preferred padding despite rewriting
1707  rewriteRequired = true;
1708  }
1709  } else if(rewriteRequired) {
1710  // rewriting is forced or new ID3v2 tag is too big for available space
1711  // -> use preferred padding when rewriting anyways
1712  padding = preferredPadding();
1713  } else if(makers.empty() && flacStream && padding && padding < 4) {
1714  // no ID3v2 tag -> must include padding in FLAC stream
1715  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1716  padding = preferredPadding();
1717  rewriteRequired = true;
1718  }
1719  if(rewriteRequired && flacStream && makers.empty() && padding) {
1720  // the first 4 byte of FLAC padding actually don't count because these
1721  // can not be used for additional meta data
1722  padding += 4;
1723  }
1724  updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1725 
1726  // setup stream(s) for writing
1727  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1728  string backupPath;
1729  NativeFileStream &outputStream = stream();
1730  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1731 
1732  if(rewriteRequired) {
1733  if(m_saveFilePath.empty()) {
1734  // move current file to temp dir and reopen it as backupStream, recreate original file
1735  try {
1736  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1737  // recreate original file, define buffer variables
1738  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1739  } catch(...) {
1740  const char *what = catchIoFailure();
1741  addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1742  throwIoFailure(what);
1743  }
1744  } else {
1745  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1746  try {
1747  close();
1748  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1749  backupStream.open(path(), ios_base::in | ios_base::binary);
1750  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1751  } catch(...) {
1752  const char *what = catchIoFailure();
1753  addNotification(NotificationType::Critical, "Opening streams to write output file failed.", context);
1754  throwIoFailure(what);
1755  }
1756  }
1757 
1758  } else { // !rewriteRequired
1759  // reopen original file to ensure it is opened for writing
1760  try {
1761  close();
1762  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1763  } catch(...) {
1764  const char *what = catchIoFailure();
1765  addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
1766  throwIoFailure(what);
1767  }
1768  }
1769 
1770  // start actual writing
1771  try {
1772  if(!makers.empty()) {
1773  // write ID3v2 tags
1774  updateStatus("Writing ID3v2 tag ...");
1775  for(auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1776  i->make(outputStream, 0);
1777  }
1778  // include padding into the last ID3v2 tag
1779  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1780  }
1781 
1782  if(flacStream) {
1783  if(padding && startOfLastMetaDataBlock) {
1784  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1785  flacMetaData.seekg(startOfLastMetaDataBlock);
1786  flacMetaData.seekp(startOfLastMetaDataBlock);
1787  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1788  flacMetaData.seekg(0);
1789  }
1790 
1791  // write FLAC metadata
1792  outputStream << flacMetaData.rdbuf();
1793 
1794  // write padding
1795  if(padding) {
1796  flacStream->makePadding(outputStream, padding, true);
1797  }
1798  }
1799 
1800  if(makers.empty() && !flacStream){
1801  // just write padding (however, padding should be set to 0 in this case?)
1802  for(; padding; --padding) {
1803  outputStream.put(0);
1804  }
1805  }
1806 
1807  // copy / skip actual stream data
1808  // -> determine media data size
1809  uint64 mediaDataSize = size() - streamOffset;
1810  if(m_actualExistingId3v1Tag) {
1811  mediaDataSize -= 128;
1812  }
1813 
1814  if(rewriteRequired) {
1815  // copy data from original file
1816  switch(m_containerFormat) {
1818  updateStatus("Writing MPEG audio frames ...");
1819  break;
1820  default:
1821  updateStatus("Writing frames ...");
1822  }
1823  backupStream.seekg(streamOffset);
1824  CopyHelper<0x4000> copyHelper;
1825  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
1826  updatePercentage(1.0);
1827  } else {
1828  // just skip actual stream data
1829  outputStream.seekp(mediaDataSize, ios_base::cur);
1830  }
1831 
1832  // write ID3v1 tag
1833  if(m_id3v1Tag) {
1834  updateStatus("Writing ID3v1 tag ...");
1835  try {
1836  m_id3v1Tag->make(stream());
1837  } catch(const Failure &) {
1838  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1839  }
1840  }
1841 
1842  // handle streams
1843  if(rewriteRequired) {
1844  // report new size
1845  reportSizeChanged(outputStream.tellp());
1846  // "save as path" is now the regular path
1847  if(!saveFilePath().empty()) {
1849  m_saveFilePath.clear();
1850  }
1851  // stream is useless for further usage because it is write-only
1852  outputStream.close();
1853  } else {
1854  const auto newSize = static_cast<uint64>(outputStream.tellp());
1855  if(newSize < size()) {
1856  // file is smaller after the modification -> truncate
1857  // -> close stream before truncating
1858  outputStream.close();
1859  // -> truncate file
1860  if(truncate(path().c_str(), newSize) == 0) {
1861  reportSizeChanged(newSize);
1862  } else {
1863  addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
1864  }
1865  } else {
1866  // file is longer after the modification -> just report new size
1867  reportSizeChanged(newSize);
1868  }
1869  }
1870 
1871  } catch(...) {
1872  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, context);
1873  }
1874  }
1875 }
1876 
1877 }
std::unordered_set< std::string > availableLanguages(Media::MediaType type=Media::MediaType::Audio) const
Determines the available languages for specified media type (by default MediaType::Audio).
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.
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:255
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.
void addNotification(const Notification &notification)
This 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:45
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().
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:99
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:106
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.
std::string technicalSummary() const
Generates a short technical summary about the file&#39;s tracks.
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:34
void parseEverything()
Parses the container format, the tracks and the tag information of the current file.
The Notification class holds a notification message of a certain notification type.
Definition: notification.h:43
Id3v2Tag * createId3v2Tag()
Creates an ID3v2 tag for the current file.
void removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
TAG_PARSER_EXPORT const char * description()
void clearParsingResults()
Clears all parsing results and assigned/created/changed information such as detected container format...
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
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 method is meant to be called by the derived class to add all notifications from another StatusPr...
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:461
bool hasId3v1Tag() const
Returns an indication whether an ID3v1 tag is assigned.
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
ParsingStatus chaptersParsingStatus() const
Returns whether the chapters have been parsed yet.