Tag Parser  6.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1 #include "./mediafileinfo.h"
2 #include "./exceptions.h"
3 #include "./tag.h"
4 #include "./signature.h"
5 #include "./abstracttrack.h"
6 #include "./backuphelper.h"
7 
8 #include "./id3/id3v1tag.h"
9 #include "./id3/id3v2tag.h"
10 
11 #include "./wav/waveaudiostream.h"
12 
14 
15 #include "./adts/adtsstream.h"
16 
17 #include "./mp4/mp4container.h"
18 #include "./mp4/mp4atom.h"
19 #include "./mp4/mp4tag.h"
20 #include "./mp4/mp4ids.h"
21 #include "./mp4/mp4track.h"
22 
23 #include "./matroska/ebmlelement.h"
25 #include "./matroska/matroskatag.h"
27 
28 #include "./ogg/oggcontainer.h"
29 
30 #include "./flac/flacstream.h"
31 #include "./flac/flacmetadata.h"
32 
33 #include <c++utilities/conversion/stringconversion.h>
34 #include <c++utilities/io/catchiofailure.h>
35 #include <c++utilities/chrono/timespan.h>
36 
37 #include <unistd.h>
38 
39 #include <cstdio>
40 #include <algorithm>
41 #include <iomanip>
42 #include <ios>
43 #include <system_error>
44 #include <functional>
45 #include <memory>
46 
47 using namespace std;
48 using namespace std::placeholders;
49 using namespace IoUtilities;
50 using namespace ConversionUtilities;
51 using namespace ChronoUtilities;
52 
58 namespace Media {
59 
60 #ifdef FORCE_FULL_PARSE_DEFAULT
61 # define MEDIAINFO_CPP_FORCE_FULL_PARSE true
62 #else
63 # define MEDIAINFO_CPP_FORCE_FULL_PARSE false
64 #endif
65 
79 MediaFileInfo::MediaFileInfo() :
80  m_containerParsingStatus(ParsingStatus::NotParsedYet),
81  m_containerFormat(ContainerFormat::Unknown),
82  m_containerOffset(0),
83  m_actualExistingId3v1Tag(false),
84  m_tracksParsingStatus(ParsingStatus::NotParsedYet),
85  m_tagsParsingStatus(ParsingStatus::NotParsedYet),
86  m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
87  m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
88  m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
89  m_forceRewrite(true),
90  m_minPadding(0),
91  m_maxPadding(0),
92  m_preferredPadding(0),
93  m_tagPosition(ElementPosition::BeforeData),
94  m_forceTagPosition(true),
95  m_indexPosition(ElementPosition::BeforeData),
96  m_forceIndexPosition(true)
97 {}
98 
105  BasicFileInfo(path),
106  m_containerParsingStatus(ParsingStatus::NotParsedYet),
107  m_containerFormat(ContainerFormat::Unknown),
108  m_containerOffset(0),
109  m_actualExistingId3v1Tag(false),
110  m_tracksParsingStatus(ParsingStatus::NotParsedYet),
111  m_tagsParsingStatus(ParsingStatus::NotParsedYet),
112  m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
113  m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
114  m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
115  m_forceRewrite(true),
116  m_minPadding(0),
117  m_maxPadding(0),
118  m_preferredPadding(0),
119  m_tagPosition(ElementPosition::BeforeData),
120  m_forceTagPosition(true),
121  m_indexPosition(ElementPosition::BeforeData),
122  m_forceIndexPosition(true)
123 {}
124 
129 {}
130 
147 {
149  // there's no need to read the container format twice
150  return;
151  }
152 
154  static const string context("parsing file header");
155  open(); // ensure the file is open
156  m_containerFormat = ContainerFormat::Unknown;
157 
158  // file size
159  m_paddingSize = 0;
160  m_containerOffset = 0;
161 
162  // read signatrue
163  char buff[16];
164  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
165 startParsingSignature:
166  if(size() - m_containerOffset >= 16) {
167  stream().seekg(m_containerOffset, ios_base::beg);
168  stream().read(buff, sizeof(buff));
169 
170  // skip zero bytes/padding
171  size_t bytesSkipped = 0;
172  for(buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped);
173  if(bytesSkipped >= 4) {
174  m_containerOffset += bytesSkipped;
175 
176  // give up after 0x100 bytes
177  if((m_paddingSize += bytesSkipped) >= 0x100u) {
178  m_containerParsingStatus = ParsingStatus::NotSupported;
179  m_containerFormat = ContainerFormat::Unknown;
180  return;
181  }
182 
183  // try again
184  goto startParsingSignature;
185  }
186  if(m_paddingSize) {
187  addNotification(NotificationType::Warning, numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
188  }
189 
190  // parse signature
191  switch((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
193  // save position of ID3v2 tag
194  m_actualId3v2TagOffsets.push_back(m_containerOffset);
195  if(m_actualId3v2TagOffsets.size() == 2) {
196  addNotification(NotificationType::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
197  }
198 
199  // read ID3v2 header
200  stream().seekg(m_containerOffset + 5, ios_base::beg);
201  stream().read(buff, 5);
202 
203  // set the container offset to skip ID3v2 header
204  m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
205  if((*buff) & 0x10) {
206  // footer present
207  m_containerOffset += 10;
208  }
209 
210  // continue reading signature
211  goto startParsingSignature;
212 
215  // MP4/QuickTime is handled using Mp4Container instance
216  m_container = make_unique<Mp4Container>(*this, m_containerOffset);
218  try {
219  static_cast<Mp4Container *>(m_container.get())->validateElementStructure(notifications, &m_paddingSize);
220  } catch(const Failure &) {
221  m_containerParsingStatus = ParsingStatus::CriticalFailure;
222  }
223  addNotifications(notifications);
224  break;
225 
226  } case ContainerFormat::Ebml: {
227  // EBML/Matroska is handled using MatroskaContainer instance
228  auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
230  try {
232  if(container->documentType() == "matroska") {
233  m_containerFormat = ContainerFormat::Matroska;
234  } else if(container->documentType() == "webm") {
235  m_containerFormat = ContainerFormat::Webm;
236  }
237  if(m_forceFullParse) {
238  // validating the element structure of Matroska files takes too long when
239  // parsing big files so do this only when explicitely desired
240  container->validateElementStructure(notifications, &m_paddingSize);
241  container->validateIndex();
242  }
243  } catch(const Failure &) {
244  m_containerParsingStatus = ParsingStatus::CriticalFailure;
245  }
246  m_container = move(container);
247  addNotifications(notifications);
248  break;
249  } case ContainerFormat::Ogg:
250  // Ogg is handled using OggContainer instance
251  m_container = make_unique<OggContainer>(*this, m_containerOffset);
252  static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
253  break;
255  // container format is still unknown -> check for magic numbers at odd offsets
256  // -> check for tar (magic number at offset 0x101)
257  if(size() > 0x107) {
258  stream().seekg(0x101);
259  stream().read(buff, 6);
260  if(buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
261  m_containerFormat = ContainerFormat::Tar;
262  break;
263  }
264  }
265  default:
266  ;
267  }
268  }
269 
270  // set parsing status
271  if(m_containerParsingStatus == ParsingStatus::NotParsedYet) {
272  if(m_containerFormat == ContainerFormat::Unknown) {
273  m_containerParsingStatus = ParsingStatus::NotSupported;
274  } else {
275  m_containerParsingStatus = ParsingStatus::Ok;
276  }
277  }
278 }
279 
294 {
295  if(tracksParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the tracks twice
296  return;
297  }
298  static const string context("parsing tracks");
299  try {
300  if(m_container) {
301  m_container->parseTracks();
302  } else {
303  switch(m_containerFormat) {
305  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
306  break;
308  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
309  break;
311  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
312  break;
314  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
315  break;
316  default:
317  throw NotImplementedException();
318  }
319  m_singleTrack->parseHeader();
320 
321  switch(m_containerFormat) {
323  // FLAC streams might container padding
324  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
325  break;
326  default:
327  ;
328  }
329  }
330  m_tracksParsingStatus = ParsingStatus::Ok;
331  } catch(const NotImplementedException &) {
332  addNotification(NotificationType::Information, "Parsing tracks is not implemented for the container format of the file.", context);
333  m_tracksParsingStatus = ParsingStatus::NotSupported;
334  } catch(const Failure &) {
335  addNotification(NotificationType::Critical, "Unable to parse tracks.", context);
336  m_tracksParsingStatus = ParsingStatus::CriticalFailure;
337  }
338 }
339 
355 {
356  if(tagsParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the tags twice
357  return;
358  }
359  static const string context("parsing tag");
360  // check for id3v1 tag
361  if(size() >= 128) {
362  m_id3v1Tag = make_unique<Id3v1Tag>();
363  try {
364  m_id3v1Tag->parse(stream(), true);
365  m_actualExistingId3v1Tag = true;
366  } catch(const NoDataFoundException &) {
367  m_id3v1Tag.reset(); // no ID3v1 tag found
368  } catch(const Failure &) {
369  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
370  addNotification(NotificationType::Critical, "Unable to parse ID3v1 tag.", context);
371  }
372  }
373  // the offsets of the ID3v2 tags have already been parsed when parsing the container format
374  m_id3v2Tags.clear();
375  for(const auto offset : m_actualId3v2TagOffsets) {
376  auto id3v2Tag = make_unique<Id3v2Tag>();
377  stream().seekg(offset, ios_base::beg);
378  try {
379  id3v2Tag->parse(stream(), size() - offset);
380  m_paddingSize += id3v2Tag->paddingSize();
381  } catch(const NoDataFoundException &) {
382  continue;
383  } catch(const Failure &) {
384  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
385  addNotification(NotificationType::Critical, "Unable to parse ID3v2 tag.", context);
386  }
387  m_id3v2Tags.emplace_back(id3v2Tag.release());
388  }
389  if(m_container) {
390  try {
391  m_container->parseTags();
392  } catch(const NotImplementedException &) {
393  if(m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
394  // do not override parsing status from ID3 tags here
395  m_tagsParsingStatus = ParsingStatus::NotSupported;
396  }
397  addNotification(NotificationType::Information, "Parsing tags is not implemented for the container format of the file.", context);
398  } catch(const Failure &) {
399  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
400  addNotification(NotificationType::Critical, "Unable to parse tag.", context);
401  }
402  }
403  if(m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
404  // do not override error status here
405  m_tagsParsingStatus = ParsingStatus::Ok;
406  }
407 }
408 
421 {
422  if(chaptersParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the chapters twice
423  return;
424  }
425  static const string context("parsing chapters");
426  try {
427  if(m_container) {
428  m_container->parseChapters();
429  m_chaptersParsingStatus = ParsingStatus::Ok;
430  } else {
431  throw NotImplementedException();
432  }
433  } catch (const NotImplementedException &) {
434  m_chaptersParsingStatus = ParsingStatus::NotSupported;
435  addNotification(NotificationType::Information, "Parsing chapters is not implemented for the container format of the file.", context);
436  } catch (const Failure &) {
437  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
438  addNotification(NotificationType::Critical, "Unable to parse chapters.", context);
439  }
440 }
441 
454 {
455  if(attachmentsParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the attachments twice
456  return;
457  }
458  static const string context("parsing attachments");
459  try {
460  if(m_container) {
461  m_container->parseAttachments();
462  m_attachmentsParsingStatus = ParsingStatus::Ok;
463  } else {
464  throw NotImplementedException();
465  }
466  } catch (const NotImplementedException &) {
467  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
468  addNotification(NotificationType::Information, "Parsing attachments is not implemented for the container format of the file.", context);
469  } catch (const Failure &) {
470  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
471  addNotification(NotificationType::Critical, "Unable to parse attachments.", context);
472  }
473 }
474 
482 {
484  parseTracks();
485  parseTags();
486  parseChapters();
488 }
489 
511 bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagUsage id3v1usage, TagUsage id3v2usage, bool id3InitOnCreate, bool id3TransferValuesOnRemoval, bool mergeMultipleSuccessiveId3v2Tags, bool keepExistingId3v2version, byte id3v2MajorVersion, const std::vector<TagTarget> &requiredTargets)
512 {
513  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
515  return false;
516  }
517  // check if tags need to be created/adjusted/removed
518  bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
519  bool targetsSupported = false;
520  if(areTagsSupported() && m_container) {
521  // container object takes care of tag management
522  if(targetsRequired) {
523  // check whether container supports targets
524  if(m_container->tagCount()) {
525  // all tags in the container should support targets if the first one supports targets
526  targetsSupported = m_container->tag(0)->supportsTarget();
527  } else {
528  // try to create a new tag and check whether targets are supported
529  auto *tag = m_container->createTag();
530  if(tag) {
531  if((targetsSupported = tag->supportsTarget())) {
532  tag->setTarget(requiredTargets.front());
533  }
534  }
535  }
536  if(targetsSupported) {
537  for(const auto &target : requiredTargets) {
538  m_container->createTag(target);
539  }
540  }
541  } else {
542  // no targets are required -> just ensure that at least one tag is present
543  m_container->createTag();
544  }
545  } else {
546  // no container object present
547  if(m_containerFormat == ContainerFormat::Flac) {
548  // creation of Vorbis comment is possible
549  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
550  } else {
551  // creation of ID3 tag is possible
552  if(!hasAnyTag() && !treatUnknownFilesAsMp3Files) {
553  switch(containerFormat()) {
556  break;
557  default:
558  return false;
559  }
560  }
561  // create ID3 tags according to id3v2usage and id3v2usage
562  if(id3v1usage == TagUsage::Always) {
563  // always create ID3v1 tag -> ensure there is one
564  if(!id3v1Tag()) {
566  if(id3InitOnCreate) {
567  for(const auto &id3v2Tag : id3v2Tags()) {
568  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
569  id3v1Tag->insertValues(*id3v2Tag, true);
570  // ID3v1 does not support all text encodings which might be used in ID3v2
572  }
573  }
574  }
575  }
576  if(id3v2usage == TagUsage::Always) {
577  // always create ID3v2 tag -> ensure there is one and set version
578  if(!hasId3v2Tag()) {
579  Id3v2Tag *id3v2Tag = createId3v2Tag();
580  id3v2Tag->setVersion(id3v2MajorVersion, 0);
581  if(id3InitOnCreate && id3v1Tag()) {
582  id3v2Tag->insertValues(*id3v1Tag(), true);
583  }
584  }
585  }
586  }
587 
588  if(mergeMultipleSuccessiveId3v2Tags) {
589  mergeId3v2Tags();
590  }
591  // remove ID3 tags according to id3v2usage and id3v2usage
592  if(id3v1usage == TagUsage::Never) {
593  if(hasId3v1Tag()) {
594  // transfer tags to ID3v2 tag before removing
595  if(id3TransferValuesOnRemoval && hasId3v2Tag()) {
596  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
597  }
598  removeId3v1Tag();
599  }
600  }
601  if(id3v2usage == TagUsage::Never) {
602  if(id3TransferValuesOnRemoval && hasId3v1Tag()) {
603  // transfer tags to ID3v1 tag before removing
604  for(const auto &tag : id3v2Tags()) {
605  id3v1Tag()->insertValues(*tag, false);
606  }
607  }
609  } else if(!keepExistingId3v2version) {
610  // set version of ID3v2 tag according user preferences
611  for(const auto &tag : id3v2Tags()) {
612  tag->setVersion(id3v2MajorVersion, 0);
613  }
614  }
615  }
616  if(targetsRequired && !targetsSupported) {
617  addNotification(NotificationType::Information, "The container/tags do not support targets. The specified targets are ignored.", "creating tags");
618  }
619  return true;
620 }
621 
622 
645 {
646  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) {
717  // check whether only Opus tracks are present
718  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
719  for(const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
720  if(track->format().general != GeneralMediaFormat::Opus) {
721  version = 0;
722  break;
723  }
724  }
728  break;
730  if(m_singleTrack) {
731  version = m_singleTrack->format().sub;
732  }
733  break;
734  default:
735  ;
736  }
737  return Media::containerFormatAbbreviation(m_containerFormat, mediaType, version);
738 }
739 
750 const char *MediaFileInfo::mimeType() const
751 {
752  MediaType mediaType;
753  switch(m_containerFormat) {
758  break;
759  default:
760  mediaType = MediaType::Unknown;
761  }
762  return Media::containerMimeType(m_containerFormat, mediaType);
763 }
764 
777 vector<AbstractTrack *> MediaFileInfo::tracks() const
778 {
779  vector<AbstractTrack *> res;
780  size_t trackCount = 0;
781  size_t containerTrackCount = 0;
782  if(m_singleTrack) {
783  trackCount = 1;
784  }
785  if(m_container) {
786  trackCount += (containerTrackCount = m_container->trackCount());
787  }
788  res.reserve(trackCount);
789 
790  if(m_singleTrack) {
791  res.push_back(m_singleTrack.get());
792  }
793  for(size_t i = 0; i != containerTrackCount; ++i) {
794  res.push_back(m_container->track(i));
795  }
796  return res;
797 }
798 
808 {
810  if(m_singleTrack && m_singleTrack->mediaType() == type) {
811  return true;
812  } else if(m_container) {
813  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
814  if(m_container->track(i)->mediaType() == type) {
815  return true;
816  }
817  }
818  }
819  }
820  return false;
821 }
822 
833 {
834  if(m_container) {
835  return m_container->duration();
836  } else if(m_singleTrack) {
837  return m_singleTrack->duration();
838  }
839  return TimeSpan();
840 }
841 
852 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
853 {
854  unordered_set<string> res;
855  if(m_container) {
856  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
857  const AbstractTrack *track = m_container->track(i);
858  if((type == MediaType::Unknown || track->mediaType() == type) && !track->language().empty() && track->language() != "und") {
859  res.emplace(track->language());
860  }
861  }
862  } else if(m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty() && m_singleTrack->language() != "und") {
863  res.emplace(m_singleTrack->language());
864  }
865  return res;
866 }
867 
880 {
881  if(m_container) {
882  const size_t trackCount = m_container->trackCount();
883  vector<string> parts;
884  parts.reserve(trackCount);
885  for(size_t i = 0; i != trackCount; ++i) {
886  const string description(m_container->track(i)->description());
887  if(!description.empty()) {
888  parts.emplace_back(move(description));
889  }
890  }
891  return joinStrings(parts, " / ");
892  } else if(m_singleTrack) {
893  return m_singleTrack->description();
894  }
895  return string();
896 }
897 
908 {
910  if(m_id3v1Tag) {
911  m_id3v1Tag.reset();
912  return true;
913  }
914  }
915  return false;
916 }
917 
934 {
936  return nullptr;
937  }
938  if(!m_id3v1Tag) {
939  m_id3v1Tag = make_unique<Id3v1Tag>();
940  }
941  return m_id3v1Tag.get();
942 }
943 
958 {
960  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
961  if(i->get() == tag) {
962  m_id3v2Tags.erase(i);
963  return true;
964  }
965  }
966  }
967  return false;
968 }
969 
979 {
980  if(tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
981  return false;
982  }
983  m_id3v2Tags.clear();
984  return true;
985 }
986 
1003 {
1004  if(m_id3v2Tags.empty()) {
1005  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1006  }
1007  return m_id3v2Tags.front().get();
1008 }
1009 
1023 {
1024  if(tag) {
1025  if(m_container) {
1026  m_container->removeTag(tag);
1027  }
1028  if(m_id3v1Tag.get() == tag) {
1029  m_id3v1Tag.reset();
1030  }
1031  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1032  if(i->get() == tag) {
1033  m_id3v2Tags.erase(i);
1034  break;
1035  }
1036  }
1037  }
1038 }
1039 
1046 {
1047  if(m_container) {
1048  m_container->removeAllTags();
1049  }
1050  if(m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1051  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1052  }
1053  m_id3v1Tag.reset();
1054  m_id3v2Tags.clear();
1055 }
1056 
1061 {
1062  if(m_container && m_container->chapterCount()) {
1063  return true;
1064  }
1065  switch(m_containerFormat) {
1067  case ContainerFormat::Webm:
1068  return true;
1069  default:
1070  return false;
1071  }
1072 }
1073 
1078 {
1079  if(m_container && m_container->attachmentCount()) {
1080  return true;
1081  }
1082  switch(m_containerFormat) {
1084  case ContainerFormat::Webm:
1085  return true;
1086  default:
1087  return false;
1088  }
1089 }
1090 
1095 {
1096  if(trackCount()) {
1097  return true;
1098  }
1099  switch(m_containerFormat) {
1100  case ContainerFormat::Mp4:
1103  case ContainerFormat::Ogg:
1105  case ContainerFormat::Webm:
1106  return true;
1107  default:
1108  return false;
1109  }
1110 }
1111 
1116 {
1117  switch(m_containerFormat) {
1118  case ContainerFormat::Adts:
1119  case ContainerFormat::Flac:
1122  case ContainerFormat::Mp4:
1123  case ContainerFormat::Ogg:
1124  case ContainerFormat::Webm:
1125  // these container formats are supported
1126  return true;
1127  default:
1128  // the container format is unsupported
1129  // -> an ID3 tag might be already present, in this case the tags are considered supported
1130  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1131  }
1132 }
1133 
1140 {
1141  // simply return the first tag here since MP4 files never contain multiple tags
1142  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;
1143 }
1144 
1151 const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
1152 {
1153  // matroska files might contain multiple tags (targeting different scopes)
1154  if(m_containerFormat == ContainerFormat::Matroska && m_container) {
1155  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1156  } else {
1157  static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1158  return empty;
1159  }
1160 }
1161 
1168 {
1169  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1170  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1171  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack
1172  ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()
1173  : nullptr);
1174 }
1175 
1181 vector<AbstractChapter *> MediaFileInfo::chapters() const
1182 {
1183  vector<AbstractChapter *> res;
1184  if(m_container) {
1185  const size_t count = m_container->chapterCount();
1186  res.reserve(count);
1187  for(size_t i = 0; i != count; ++i) {
1188  res.push_back(m_container->chapter(i));
1189  }
1190  }
1191  return res;
1192 }
1193 
1199 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1200 {
1201  vector<AbstractAttachment *> res;
1202  if(m_container) {
1203  const size_t count = m_container->attachmentCount();
1204  res.reserve(count);
1205  for(size_t i = 0; i != count; ++i) {
1206  res.push_back(m_container->attachment(i));
1207  }
1208  }
1209  return res;
1210 }
1211 
1217 {
1218  if(m_container) {
1219  if(m_container->hasNotifications()) {
1220  return true;
1221  }
1222  }
1223  for(const auto *track : tracks()) {
1224  if(track->hasNotifications()) {
1225  return true;
1226  }
1227  }
1228  for(const auto *tag : tags()) {
1229  if(tag->hasNotifications()) {
1230  return true;
1231  }
1232  }
1233  for(const auto *chapter : chapters()) {
1234  if(chapter->hasNotifications()) {
1235  return true;
1236  }
1237  }
1238  return false;
1239 }
1240 
1246 {
1248  if(type == Notification::worstNotificationType()) {
1249  return type;
1250  }
1251  if(m_container) {
1252  type |= m_container->worstNotificationType();
1253  }
1254  if(type == Notification::worstNotificationType()) {
1255  return type;
1256  }
1257  for(const auto *track : tracks()) {
1258  type |= track->worstNotificationType();
1259  if(type == Notification::worstNotificationType()) {
1260  return type;
1261  }
1262  }
1263  for(const auto *tag : tags()) {
1264  type |= tag->worstNotificationType();
1265  if(type == Notification::worstNotificationType()) {
1266  return type;
1267  }
1268  }
1269  for(const auto *chapter : chapters()) {
1270  type |= chapter->worstNotificationType();
1271  if(type == Notification::worstNotificationType()) {
1272  return type;
1273  }
1274  }
1275  return type;
1276 }
1277 
1284 {
1285  notifications.insert(notifications.end(), this->notifications().cbegin(), this->notifications().cend());
1286  if(m_container) {
1287  // prevent duplicates which might be present when validating element structure
1288  switch(m_containerFormat) {
1289  case ContainerFormat::Ebml:
1291  // those files are only validated when a full parse is forced
1292  if(!m_forceFullParse) {
1293  break;
1294  }
1295  FALLTHROUGH;
1296  case ContainerFormat::Mp4:
1298  // those files are always validated
1299  for(const Notification &notification : m_container->notifications()) {
1300  if(find(notifications.cbegin(), notifications.cend(), notification) == notifications.cend()) {
1301  notifications.emplace_back(notification);
1302  }
1303  }
1304  break;
1305  default:
1306  notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());;
1307  }
1308  }
1309  for(const auto *track : tracks()) {
1310  notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1311  }
1312  for(const auto *tag : tags()) {
1313  notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1314  }
1315  for(const auto *chapter : chapters()) {
1316  notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1317  }
1318  for(const auto *attachment : attachments()) {
1319  notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1320  }
1321 }
1322 
1328 {
1330  gatherRelatedNotifications(notifications);
1331  return notifications;
1332 }
1333 
1344 {
1345  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1346  m_containerFormat = ContainerFormat::Unknown;
1347  m_containerOffset = 0;
1348  m_paddingSize = 0;
1349  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1350  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1351  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1352  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1353  m_id3v1Tag.reset();
1354  m_id3v2Tags.clear();
1355  m_actualId3v2TagOffsets.clear();
1356  m_actualExistingId3v1Tag = false;
1357  m_container.reset();
1358  m_singleTrack.reset();
1359 }
1360 
1378 {
1379  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1380  if(begin != end) {
1381  Id3v2Tag &first = **begin;
1382  auto isecond = begin + 1;
1383  if(isecond != end) {
1384  for(auto i = isecond; i != end; ++i) {
1385  first.insertFields(**i, false);
1386  }
1387  m_id3v2Tags.erase(isecond, end - 1);
1388  }
1389  }
1390 }
1391 
1403 {
1405  return createAppropriateTags(false, TagUsage::Never, TagUsage::Always, true, true, 3);
1406  } else {
1407  return false;
1408  }
1409 }
1410 
1422 {
1424  return createAppropriateTags(false, TagUsage::Always, TagUsage::Never, true, true, 3);
1425  } else {
1426  return false;
1427  }
1428 }
1429 
1445 {
1446  switch(m_containerFormat) {
1447  case ContainerFormat::Ogg:
1448  if(m_container) {
1449  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1450  }
1451  break;
1452  case ContainerFormat::Flac:
1453  if(m_singleTrack) {
1454  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1455  }
1456  break;
1457  default:
1458  ;
1459  }
1460  return nullptr;
1461 }
1462 
1473 {
1474  switch(m_containerFormat) {
1475  case ContainerFormat::Ogg:
1476  if(m_container) {
1477  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1478  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1479  return hadTags;
1480  }
1481  break;
1482  case ContainerFormat::Flac:
1483  if(m_singleTrack) {
1484  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1485  }
1486  break;
1487  default:
1488  ;
1489  }
1490  return false;
1491 }
1492 
1501 void MediaFileInfo::tags(vector<Tag *> &tags) const
1502 {
1503  if(hasId3v1Tag()) {
1504  tags.push_back(m_id3v1Tag.get());
1505  }
1506  for(const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1507  tags.push_back(tag.get());
1508  }
1509  if(m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1510  if(auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1511  tags.push_back(vorbisComment);
1512  }
1513  }
1514  if(m_container) {
1515  for(size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1516  tags.push_back(m_container->tag(i));
1517  }
1518  }
1519 }
1520 
1525 {
1526  return hasId3v1Tag()
1527  || hasId3v2Tag()
1528  || (m_container && m_container->tagCount())
1529  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1530 }
1531 
1538 vector<Tag *> MediaFileInfo::tags() const
1539 {
1540  vector<Tag *> res;
1541  tags(res);
1542  return res;
1543 }
1544 
1549 {
1551  invalidateStatus();
1554 }
1555 
1559 void MediaFileInfo::makeMp3File()
1560 {
1561  static const string context("making MP3/FLAC file");
1562  // there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
1563  if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty() && m_containerFormat != ContainerFormat::Flac) {
1564  if(m_actualExistingId3v1Tag) {
1565  // there is currently an ID3v1 tag at the end of the file
1566  if(m_id3v1Tag) {
1567  // the file shall still have an ID3v1 tag
1568  updateStatus("Updating ID3v1 tag ...");
1569  // ensure the file is still open / not readonly
1570  open();
1571  stream().seekp(-128, ios_base::end);
1572  try {
1573  m_id3v1Tag->make(stream());
1574  } catch(const Failure &) {
1575  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1576  }
1577  } else {
1578  // the currently existing ID3v1 tag shall be removed
1579  updateStatus("Removing ID3v1 tag ...");
1580  stream().close();
1581  if(truncate(path().c_str(), size() - 128) == 0) {
1582  reportSizeChanged(size() - 128);
1583  } else {
1584  addNotification(NotificationType::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1585  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1586  }
1587  }
1588 
1589  } else {
1590  // there is currently no ID3v1 tag at the end of the file
1591  if(m_id3v1Tag) {
1592  updateStatus("Adding ID3v1 tag ...");
1593  // ensure the file is still open / not readonly
1594  open();
1595  stream().seekp(0, ios_base::end);
1596  try {
1597  m_id3v1Tag->make(stream());
1598  } catch(const Failure &) {
1599  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1600  }
1601  } else {
1602  addNotification(NotificationType::Information, "Nothing to be changed.", context);
1603  }
1604  }
1605 
1606  } else {
1607  // ID3v2 needs to be modified
1608  updateStatus("Updating ID3v2 tags ...");
1609 
1610  // prepare ID3v2 tags
1611  vector<Id3v2TagMaker> makers;
1612  makers.reserve(m_id3v2Tags.size());
1613  uint32 tagsSize = 0;
1614  for(auto &tag : m_id3v2Tags) {
1615  try {
1616  makers.emplace_back(tag->prepareMaking());
1617  tagsSize += makers.back().requiredSize();
1618  } catch(const Failure &) {
1619  // nothing to do: notifications added anyways
1620  }
1621  addNotifications(*tag);
1622  }
1623 
1624  // check whether it is a raw FLAC stream
1625  FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1626  uint32 streamOffset; // where the actual stream starts
1627  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1628  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1629  uint32 startOfLastMetaDataBlock;
1630 
1631  if(flacStream) {
1632  // if it is a raw FLAC stream, make FLAC metadata
1633  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData);
1634  tagsSize += flacMetaData.tellp();
1635  streamOffset = flacStream->streamOffset();
1636  } else {
1637  // make no further metadata, just use the container offset as stream offset
1638  streamOffset = static_cast<uint32>(m_containerOffset);
1639  }
1640 
1641  // check whether rewrite is required
1642  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1643  uint32 padding = 0;
1644  if(!rewriteRequired) {
1645  // rewriting is not forced and new tag is not too big for available space
1646  // -> calculate new padding
1647  padding = streamOffset - tagsSize;
1648  // -> check whether the new padding matches specifications
1649  if(padding < minPadding() || padding > maxPadding()) {
1650  rewriteRequired = true;
1651  }
1652  }
1653  if(makers.empty() && !flacStream) {
1654  // an ID3v2 tag is not written and it is not a FLAC stream
1655  // -> can't include padding
1656  if(padding) {
1657  // but padding would be present -> need to rewrite
1658  padding = 0; // can't write the preferred padding despite rewriting
1659  rewriteRequired = true;
1660  }
1661  } else if(rewriteRequired) {
1662  // rewriting is forced or new ID3v2 tag is too big for available space
1663  // -> use preferred padding when rewriting anyways
1664  padding = preferredPadding();
1665  } else if(makers.empty() && flacStream && padding && padding < 4) {
1666  // no ID3v2 tag -> must include padding in FLAC stream
1667  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1668  padding = preferredPadding();
1669  rewriteRequired = true;
1670  }
1671  if(rewriteRequired && flacStream && makers.empty() && padding) {
1672  // the first 4 byte of FLAC padding actually don't count because these
1673  // can not be used for additional meta data
1674  padding += 4;
1675  }
1676  updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1677 
1678  // setup stream(s) for writing
1679  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1680  string backupPath;
1681  NativeFileStream &outputStream = stream();
1682  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1683 
1684  if(rewriteRequired) {
1685  if(m_saveFilePath.empty()) {
1686  // move current file to temp dir and reopen it as backupStream, recreate original file
1687  try {
1688  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1689  // recreate original file, define buffer variables
1690  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1691  } catch(...) {
1692  const char *what = catchIoFailure();
1693  addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1694  throwIoFailure(what);
1695  }
1696  } else {
1697  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1698  try {
1699  close();
1700  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1701  backupStream.open(path(), ios_base::in | ios_base::binary);
1702  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1703  } catch(...) {
1704  const char *what = catchIoFailure();
1705  addNotification(NotificationType::Critical, "Opening streams to write output file failed.", context);
1706  throwIoFailure(what);
1707  }
1708  }
1709 
1710  } else { // !rewriteRequired
1711  // reopen original file to ensure it is opened for writing
1712  try {
1713  close();
1714  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1715  } catch(...) {
1716  const char *what = catchIoFailure();
1717  addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
1718  throwIoFailure(what);
1719  }
1720  }
1721 
1722  // start actual writing
1723  try {
1724  if(!makers.empty()) {
1725  // write ID3v2 tags
1726  updateStatus("Writing ID3v2 tag ...");
1727  for(auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1728  i->make(outputStream, 0);
1729  }
1730  // include padding into the last ID3v2 tag
1731  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1732  }
1733 
1734  if(flacStream) {
1735  if(padding && startOfLastMetaDataBlock) {
1736  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1737  flacMetaData.seekg(startOfLastMetaDataBlock);
1738  flacMetaData.seekp(startOfLastMetaDataBlock);
1739  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1740  flacMetaData.seekg(0);
1741  }
1742 
1743  // write FLAC metadata
1744  outputStream << flacMetaData.rdbuf();
1745 
1746  // write padding
1747  if(padding) {
1748  flacStream->makePadding(outputStream, padding, true);
1749  }
1750  }
1751 
1752  if(makers.empty() && !flacStream){
1753  // just write padding (however, padding should be set to 0 in this case?)
1754  for(; padding; --padding) {
1755  outputStream.put(0);
1756  }
1757  }
1758 
1759  // copy / skip actual stream data
1760  // -> determine media data size
1761  uint64 mediaDataSize = size() - streamOffset;
1762  if(m_actualExistingId3v1Tag) {
1763  mediaDataSize -= 128;
1764  }
1765 
1766  if(rewriteRequired) {
1767  // copy data from original file
1768  switch(m_containerFormat) {
1770  updateStatus("Writing MPEG audio frames ...");
1771  break;
1772  default:
1773  updateStatus("Writing frames ...");
1774  }
1775  backupStream.seekg(streamOffset);
1776  CopyHelper<0x4000> copyHelper;
1777  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
1778  updatePercentage(100.0);
1779  } else {
1780  // just skip actual stream data
1781  outputStream.seekp(mediaDataSize, ios_base::cur);
1782  }
1783 
1784  // write ID3v1 tag
1785  if(m_id3v1Tag) {
1786  updateStatus("Writing ID3v1 tag ...");
1787  try {
1788  m_id3v1Tag->make(stream());
1789  } catch(const Failure &) {
1790  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1791  }
1792  }
1793 
1794  // handle streams
1795  if(rewriteRequired) {
1796  // report new size
1797  reportSizeChanged(outputStream.tellp());
1798  // "save as path" is now the regular path
1799  if(!saveFilePath().empty()) {
1801  m_saveFilePath.clear();
1802  }
1803  // stream is useless for further usage because it is write-only
1804  outputStream.close();
1805  } else {
1806  const auto newSize = static_cast<uint64>(outputStream.tellp());
1807  if(newSize < size()) {
1808  // file is smaller after the modification -> truncate
1809  // -> close stream before truncating
1810  outputStream.close();
1811  // -> truncate file
1812  if(truncate(path().c_str(), newSize) == 0) {
1813  reportSizeChanged(newSize);
1814  } else {
1815  addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
1816  }
1817  } else {
1818  // file is longer after the modification -> just report new size
1819  reportSizeChanged(newSize);
1820  }
1821  }
1822 
1823  } catch(...) {
1824  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, context);
1825  }
1826  }
1827 }
1828 
1829 }
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.
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:243
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: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().
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: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:103
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 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 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:438
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.