Tag Parser  6.4.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1 #include "./mediafileinfo.h"
2 #include "./exceptions.h"
3 #include "./tag.h"
4 #include "./signature.h"
5 #include "./abstracttrack.h"
6 #include "./backuphelper.h"
7 
8 #include "./id3/id3v1tag.h"
9 #include "./id3/id3v2tag.h"
10 
11 #include "./wav/waveaudiostream.h"
12 
14 
15 #include "./adts/adtsstream.h"
16 
17 #include "./mp4/mp4container.h"
18 #include "./mp4/mp4atom.h"
19 #include "./mp4/mp4tag.h"
20 #include "./mp4/mp4ids.h"
21 #include "./mp4/mp4track.h"
22 
23 #include "./matroska/ebmlelement.h"
25 #include "./matroska/matroskatag.h"
27 
28 #include "./ogg/oggcontainer.h"
29 
30 #include "./flac/flacstream.h"
31 #include "./flac/flacmetadata.h"
32 
33 #include <c++utilities/conversion/stringconversion.h>
34 #include <c++utilities/io/catchiofailure.h>
35 #include <c++utilities/chrono/timespan.h>
36 
37 #include <unistd.h>
38 
39 #include <cstdio>
40 #include <algorithm>
41 #include <iomanip>
42 #include <ios>
43 #include <system_error>
44 #include <functional>
45 #include <memory>
46 
47 using namespace std;
48 using namespace std::placeholders;
49 using namespace IoUtilities;
50 using namespace ConversionUtilities;
51 using namespace ChronoUtilities;
52 
58 namespace Media {
59 
60 #ifdef FORCE_FULL_PARSE_DEFAULT
61 # define MEDIAINFO_CPP_FORCE_FULL_PARSE true
62 #else
63 # define MEDIAINFO_CPP_FORCE_FULL_PARSE false
64 #endif
65 
79 MediaFileInfo::MediaFileInfo() :
80  m_containerParsingStatus(ParsingStatus::NotParsedYet),
81  m_containerFormat(ContainerFormat::Unknown),
82  m_containerOffset(0),
83  m_actualExistingId3v1Tag(false),
84  m_tracksParsingStatus(ParsingStatus::NotParsedYet),
85  m_tagsParsingStatus(ParsingStatus::NotParsedYet),
86  m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
87  m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
88  m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
89  m_forceRewrite(true),
90  m_minPadding(0),
91  m_maxPadding(0),
92  m_preferredPadding(0),
93  m_tagPosition(ElementPosition::BeforeData),
94  m_forceTagPosition(true),
95  m_indexPosition(ElementPosition::BeforeData),
96  m_forceIndexPosition(true)
97 {}
98 
105  BasicFileInfo(path),
106  m_containerParsingStatus(ParsingStatus::NotParsedYet),
107  m_containerFormat(ContainerFormat::Unknown),
108  m_containerOffset(0),
109  m_actualExistingId3v1Tag(false),
110  m_tracksParsingStatus(ParsingStatus::NotParsedYet),
111  m_tagsParsingStatus(ParsingStatus::NotParsedYet),
112  m_chaptersParsingStatus(ParsingStatus::NotParsedYet),
113  m_attachmentsParsingStatus(ParsingStatus::NotParsedYet),
114  m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE),
115  m_forceRewrite(true),
116  m_minPadding(0),
117  m_maxPadding(0),
118  m_preferredPadding(0),
119  m_tagPosition(ElementPosition::BeforeData),
120  m_forceTagPosition(true),
121  m_indexPosition(ElementPosition::BeforeData),
122  m_forceIndexPosition(true)
123 {}
124 
129 {}
130 
147 {
149  // there's no need to read the container format twice
150  return;
151  }
152 
154  static const string context("parsing file header");
155  open(); // ensure the file is open
156  m_containerFormat = ContainerFormat::Unknown;
157 
158  // file size
159  m_paddingSize = 0;
160  m_containerOffset = 0;
161 
162  // read signatrue
163  char buff[16];
164  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
165 startParsingSignature:
166  if(size() - m_containerOffset >= 16) {
167  stream().seekg(m_containerOffset, ios_base::beg);
168  stream().read(buff, sizeof(buff));
169 
170  // skip zero bytes/padding
171  size_t bytesSkipped = 0;
172  for(buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped);
173  if(bytesSkipped >= 4) {
174  m_containerOffset += bytesSkipped;
175 
176  // give up after 0x100 bytes
177  if((m_paddingSize += bytesSkipped) >= 0x100u) {
178  m_containerParsingStatus = ParsingStatus::NotSupported;
179  m_containerFormat = ContainerFormat::Unknown;
180  return;
181  }
182 
183  // try again
184  goto startParsingSignature;
185  }
186  if(m_paddingSize) {
187  addNotification(NotificationType::Warning, numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
188  }
189 
190  // parse signature
191  switch((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
193  // save position of ID3v2 tag
194  m_actualId3v2TagOffsets.push_back(m_containerOffset);
195  if(m_actualId3v2TagOffsets.size() == 2) {
196  addNotification(NotificationType::Warning, "There is more 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 
644 {
645  static const string context("making file");
646  addNotification(NotificationType::Information, "Changes are about to be applied.", context);
647  bool previousParsingSuccessful = true;
648  switch(tagsParsingStatus()) {
649  case ParsingStatus::Ok:
651  break;
652  default:
653  previousParsingSuccessful = false;
654  addNotification(NotificationType::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
655  }
656  switch(tracksParsingStatus()) {
657  case ParsingStatus::Ok:
659  break;
660  default:
661  previousParsingSuccessful = false;
662  addNotification(NotificationType::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
663  }
664  if(!previousParsingSuccessful) {
665  throw InvalidDataException();
666  }
667  if(m_container) { // container object takes care
668  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
669  if(hasId3v1Tag()) {
670  addNotification(NotificationType::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
671  }
672  if(hasId3v2Tag()) {
673  addNotification(NotificationType::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
674  }
675  m_container->forwardStatusUpdateCalls(this);
676  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
677  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
678  try {
679  m_container->makeFile();
680  } catch(...) {
681  // since the file might be messed up, invalidate the parsing results
683  throw;
684  }
685  } else { // implementation if no container object is present
686  // assume the file is a MP3 file
687  try {
688  makeMp3File();
689  } catch(...) {
690  // since the file might be messed up, invalidate the parsing results
692  throw;
693  }
694  }
696 }
697 
711 {
712  MediaType mediaType = MediaType::Unknown;
713  unsigned int version = 0;
714  switch(m_containerFormat) {
716  // check whether only Opus tracks are present
717  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
718  for(const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
719  if(track->format().general != GeneralMediaFormat::Opus) {
720  version = 0;
721  break;
722  }
723  }
727  break;
729  if(m_singleTrack) {
730  version = m_singleTrack->format().sub;
731  }
732  break;
733  default:
734  ;
735  }
736  return Media::containerFormatAbbreviation(m_containerFormat, mediaType, version);
737 }
738 
749 const char *MediaFileInfo::mimeType() const
750 {
751  MediaType mediaType;
752  switch(m_containerFormat) {
757  break;
758  default:
759  mediaType = MediaType::Unknown;
760  }
761  return Media::containerMimeType(m_containerFormat, mediaType);
762 }
763 
776 vector<AbstractTrack *> MediaFileInfo::tracks() const
777 {
778  vector<AbstractTrack *> res;
779  size_t trackCount = 0;
780  size_t containerTrackCount = 0;
781  if(m_singleTrack) {
782  trackCount = 1;
783  }
784  if(m_container) {
785  trackCount += (containerTrackCount = m_container->trackCount());
786  }
787  res.reserve(trackCount);
788 
789  if(m_singleTrack) {
790  res.push_back(m_singleTrack.get());
791  }
792  for(size_t i = 0; i != containerTrackCount; ++i) {
793  res.push_back(m_container->track(i));
794  }
795  return res;
796 }
797 
807 {
809  if(m_singleTrack && m_singleTrack->mediaType() == type) {
810  return true;
811  } else if(m_container) {
812  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
813  if(m_container->track(i)->mediaType() == type) {
814  return true;
815  }
816  }
817  }
818  }
819  return false;
820 }
821 
832 {
833  if(m_container) {
834  return m_container->duration();
835  } else if(m_singleTrack) {
836  return m_singleTrack->duration();
837  }
838  return TimeSpan();
839 }
840 
851 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
852 {
853  unordered_set<string> res;
854  if(m_container) {
855  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
856  const AbstractTrack *track = m_container->track(i);
857  if((type == MediaType::Unknown || track->mediaType() == type) && !track->language().empty() && track->language() != "und") {
858  res.emplace(track->language());
859  }
860  }
861  } else if(m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty() && m_singleTrack->language() != "und") {
862  res.emplace(m_singleTrack->language());
863  }
864  return res;
865 }
866 
879 {
880  if(m_container) {
881  const size_t trackCount = m_container->trackCount();
882  vector<string> parts;
883  parts.reserve(trackCount);
884  for(size_t i = 0; i != trackCount; ++i) {
885  const string description(m_container->track(i)->description());
886  if(!description.empty()) {
887  parts.emplace_back(move(description));
888  }
889  }
890  return joinStrings(parts, " / ");
891  } else if(m_singleTrack) {
892  return m_singleTrack->description();
893  }
894  return string();
895 }
896 
907 {
909  if(m_id3v1Tag) {
910  m_id3v1Tag.reset();
911  return true;
912  }
913  }
914  return false;
915 }
916 
933 {
935  return nullptr;
936  }
937  if(!m_id3v1Tag) {
938  m_id3v1Tag = make_unique<Id3v1Tag>();
939  }
940  return m_id3v1Tag.get();
941 }
942 
957 {
959  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
960  if(i->get() == tag) {
961  m_id3v2Tags.erase(i);
962  return true;
963  }
964  }
965  }
966  return false;
967 }
968 
978 {
979  if(tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
980  return false;
981  }
982  m_id3v2Tags.clear();
983  return true;
984 }
985 
1002 {
1003  if(m_id3v2Tags.empty()) {
1004  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1005  }
1006  return m_id3v2Tags.front().get();
1007 }
1008 
1022 {
1023  if(tag) {
1024  if(m_container) {
1025  m_container->removeTag(tag);
1026  }
1027  if(m_id3v1Tag.get() == tag) {
1028  m_id3v1Tag.reset();
1029  }
1030  for(auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1031  if(i->get() == tag) {
1032  m_id3v2Tags.erase(i);
1033  break;
1034  }
1035  }
1036  }
1037 }
1038 
1045 {
1046  if(m_container) {
1047  m_container->removeAllTags();
1048  }
1049  if(m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1050  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1051  }
1052  m_id3v1Tag.reset();
1053  m_id3v2Tags.clear();
1054 }
1055 
1060 {
1061  if(m_container && m_container->chapterCount()) {
1062  return true;
1063  }
1064  switch(m_containerFormat) {
1066  case ContainerFormat::Webm:
1067  return true;
1068  default:
1069  return false;
1070  }
1071 }
1072 
1077 {
1078  if(m_container && m_container->attachmentCount()) {
1079  return true;
1080  }
1081  switch(m_containerFormat) {
1083  case ContainerFormat::Webm:
1084  return true;
1085  default:
1086  return false;
1087  }
1088 }
1089 
1094 {
1095  if(trackCount()) {
1096  return true;
1097  }
1098  switch(m_containerFormat) {
1099  case ContainerFormat::Mp4:
1102  case ContainerFormat::Ogg:
1104  case ContainerFormat::Webm:
1105  return true;
1106  default:
1107  return false;
1108  }
1109 }
1110 
1115 {
1116  switch(m_containerFormat) {
1117  case ContainerFormat::Adts:
1118  case ContainerFormat::Flac:
1121  case ContainerFormat::Mp4:
1122  case ContainerFormat::Ogg:
1123  case ContainerFormat::Webm:
1124  // these container formats are supported
1125  return true;
1126  default:
1127  // the container format is unsupported
1128  // -> an ID3 tag might be already present, in this case the tags are considered supported
1129  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1130  }
1131 }
1132 
1139 {
1140  // simply return the first tag here since MP4 files never contain multiple tags
1141  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;
1142 }
1143 
1150 const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
1151 {
1152  // matroska files might contain multiple tags (targeting different scopes)
1153  if(m_containerFormat == ContainerFormat::Matroska && m_container) {
1154  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1155  } else {
1156  static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1157  return empty;
1158  }
1159 }
1160 
1167 {
1168  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1169  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1170  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack
1171  ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()
1172  : nullptr);
1173 }
1174 
1180 vector<AbstractChapter *> MediaFileInfo::chapters() const
1181 {
1182  vector<AbstractChapter *> res;
1183  if(m_container) {
1184  const size_t count = m_container->chapterCount();
1185  res.reserve(count);
1186  for(size_t i = 0; i != count; ++i) {
1187  res.push_back(m_container->chapter(i));
1188  }
1189  }
1190  return res;
1191 }
1192 
1198 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1199 {
1200  vector<AbstractAttachment *> res;
1201  if(m_container) {
1202  const size_t count = m_container->attachmentCount();
1203  res.reserve(count);
1204  for(size_t i = 0; i != count; ++i) {
1205  res.push_back(m_container->attachment(i));
1206  }
1207  }
1208  return res;
1209 }
1210 
1216 {
1217  if(m_container) {
1218  if(m_container->hasNotifications()) {
1219  return true;
1220  }
1221  }
1222  for(const auto *track : tracks()) {
1223  if(track->hasNotifications()) {
1224  return true;
1225  }
1226  }
1227  for(const auto *tag : tags()) {
1228  if(tag->hasNotifications()) {
1229  return true;
1230  }
1231  }
1232  for(const auto *chapter : chapters()) {
1233  if(chapter->hasNotifications()) {
1234  return true;
1235  }
1236  }
1237  return false;
1238 }
1239 
1245 {
1247  if(type == Notification::worstNotificationType()) {
1248  return type;
1249  }
1250  if(m_container) {
1251  type |= m_container->worstNotificationType();
1252  }
1253  if(type == Notification::worstNotificationType()) {
1254  return type;
1255  }
1256  for(const auto *track : tracks()) {
1257  type |= track->worstNotificationType();
1258  if(type == Notification::worstNotificationType()) {
1259  return type;
1260  }
1261  }
1262  for(const auto *tag : tags()) {
1263  type |= tag->worstNotificationType();
1264  if(type == Notification::worstNotificationType()) {
1265  return type;
1266  }
1267  }
1268  for(const auto *chapter : chapters()) {
1269  type |= chapter->worstNotificationType();
1270  if(type == Notification::worstNotificationType()) {
1271  return type;
1272  }
1273  }
1274  return type;
1275 }
1276 
1283 {
1284  notifications.insert(notifications.end(), this->notifications().cbegin(), this->notifications().cend());
1285  if(m_container) {
1286  // prevent duplicates which might be present when validating element structure
1287  switch(m_containerFormat) {
1288  case ContainerFormat::Ebml:
1290  // those files are only validated when a full parse is forced
1291  if(!m_forceFullParse) {
1292  notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
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 
1347 {
1348  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1349  m_containerFormat = ContainerFormat::Unknown;
1350  m_containerOffset = 0;
1351  m_paddingSize = 0;
1352  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1353  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1354  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1355  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1356  if(m_id3v1Tag) {
1357  transferNotifications(*m_id3v1Tag);
1358  m_id3v1Tag.reset();
1359  }
1360  for(auto &id3v2Tag : m_id3v2Tags) {
1361  transferNotifications(*id3v2Tag);
1362  }
1363  m_id3v2Tags.clear();
1364  m_actualId3v2TagOffsets.clear();
1365  m_actualExistingId3v1Tag = false;
1366  if(m_container) {
1367  transferNotifications(*m_container);
1368  for(size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
1369  transferNotifications(*m_container->track(i));
1370  }
1371  for(size_t i = 0, count = m_container->tagCount(); i != count; ++i) {
1372  transferNotifications(*m_container->tag(i));
1373  }
1374  for(size_t i = 0, count = m_container->chapterCount(); i != count; ++i) {
1375  transferNotifications(*m_container->chapter(i));
1376  }
1377  for(size_t i = 0, count = m_container->attachmentCount(); i != count; ++i) {
1378  transferNotifications(*m_container->attachment(i));
1379  }
1380  m_container.reset();
1381  }
1382  if(m_singleTrack) {
1383  transferNotifications(*m_singleTrack);
1384  m_singleTrack.reset();
1385  }
1386 }
1387 
1405 {
1406  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1407  if(begin != end) {
1408  Id3v2Tag &first = **begin;
1409  auto isecond = begin + 1;
1410  if(isecond != end) {
1411  for(auto i = isecond; i != end; ++i) {
1412  first.insertFields(**i, false);
1413  }
1414  m_id3v2Tags.erase(isecond, end - 1);
1415  }
1416  }
1417 }
1418 
1430 {
1432  return createAppropriateTags(false, TagUsage::Never, TagUsage::Always, true, true, 3);
1433  } else {
1434  return false;
1435  }
1436 }
1437 
1449 {
1451  return createAppropriateTags(false, TagUsage::Always, TagUsage::Never, true, true, 3);
1452  } else {
1453  return false;
1454  }
1455 }
1456 
1472 {
1473  switch(m_containerFormat) {
1474  case ContainerFormat::Ogg:
1475  if(m_container) {
1476  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1477  }
1478  break;
1479  case ContainerFormat::Flac:
1480  if(m_singleTrack) {
1481  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1482  }
1483  break;
1484  default:
1485  ;
1486  }
1487  return nullptr;
1488 }
1489 
1500 {
1501  switch(m_containerFormat) {
1502  case ContainerFormat::Ogg:
1503  if(m_container) {
1504  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1505  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1506  return hadTags;
1507  }
1508  break;
1509  case ContainerFormat::Flac:
1510  if(m_singleTrack) {
1511  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1512  }
1513  break;
1514  default:
1515  ;
1516  }
1517  return false;
1518 }
1519 
1528 void MediaFileInfo::tags(vector<Tag *> &tags) const
1529 {
1530  if(hasId3v1Tag()) {
1531  tags.push_back(m_id3v1Tag.get());
1532  }
1533  for(const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1534  tags.push_back(tag.get());
1535  }
1536  if(m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1537  if(auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1538  tags.push_back(vorbisComment);
1539  }
1540  }
1541  if(m_container) {
1542  for(size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1543  tags.push_back(m_container->tag(i));
1544  }
1545  }
1546 }
1547 
1552 {
1553  return hasId3v1Tag()
1554  || hasId3v2Tag()
1555  || (m_container && m_container->tagCount())
1556  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1557 }
1558 
1565 vector<Tag *> MediaFileInfo::tags() const
1566 {
1567  vector<Tag *> res;
1568  tags(res);
1569  return res;
1570 }
1571 
1576 {
1578  invalidateStatus();
1581 }
1582 
1586 void MediaFileInfo::makeMp3File()
1587 {
1588  static const string context("making MP3/FLAC file");
1589  // there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
1590  if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty() && m_containerFormat != ContainerFormat::Flac) {
1591  if(m_actualExistingId3v1Tag) {
1592  // there is currently an ID3v1 tag at the end of the file
1593  if(m_id3v1Tag) {
1594  // the file shall still have an ID3v1 tag
1595  updateStatus("Updating ID3v1 tag ...");
1596  // ensure the file is still open / not readonly
1597  open();
1598  stream().seekp(-128, ios_base::end);
1599  try {
1600  m_id3v1Tag->make(stream());
1601  } catch(const Failure &) {
1602  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1603  }
1604  } else {
1605  // the currently existing ID3v1 tag shall be removed
1606  updateStatus("Removing ID3v1 tag ...");
1607  stream().close();
1608  if(truncate(path().c_str(), size() - 128) == 0) {
1609  reportSizeChanged(size() - 128);
1610  } else {
1611  addNotification(NotificationType::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1612  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1613  }
1614  }
1615 
1616  } else {
1617  // there is currently no ID3v1 tag at the end of the file
1618  if(m_id3v1Tag) {
1619  updateStatus("Adding ID3v1 tag ...");
1620  // ensure the file is still open / not readonly
1621  open();
1622  stream().seekp(0, ios_base::end);
1623  try {
1624  m_id3v1Tag->make(stream());
1625  } catch(const Failure &) {
1626  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1627  }
1628  } else {
1629  addNotification(NotificationType::Information, "Nothing to be changed.", context);
1630  }
1631  }
1632 
1633  } else {
1634  // ID3v2 needs to be modified
1635  updateStatus("Updating ID3v2 tags ...");
1636 
1637  // prepare ID3v2 tags
1638  vector<Id3v2TagMaker> makers;
1639  makers.reserve(m_id3v2Tags.size());
1640  uint32 tagsSize = 0;
1641  for(auto &tag : m_id3v2Tags) {
1642  try {
1643  makers.emplace_back(tag->prepareMaking());
1644  tagsSize += makers.back().requiredSize();
1645  } catch(const Failure &) {
1646  // nothing to do: notifications added anyways
1647  }
1648  addNotifications(*tag);
1649  }
1650 
1651  // check whether it is a raw FLAC stream
1652  FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1653  uint32 streamOffset; // where the actual stream starts
1654  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1655  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1656  uint32 startOfLastMetaDataBlock;
1657 
1658  if(flacStream) {
1659  // if it is a raw FLAC stream, make FLAC metadata
1660  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData);
1661  tagsSize += flacMetaData.tellp();
1662  streamOffset = flacStream->streamOffset();
1663  } else {
1664  // make no further metadata, just use the container offset as stream offset
1665  streamOffset = static_cast<uint32>(m_containerOffset);
1666  }
1667 
1668  // check whether rewrite is required
1669  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1670  uint32 padding = 0;
1671  if(!rewriteRequired) {
1672  // rewriting is not forced and new tag is not too big for available space
1673  // -> calculate new padding
1674  padding = streamOffset - tagsSize;
1675  // -> check whether the new padding matches specifications
1676  if(padding < minPadding() || padding > maxPadding()) {
1677  rewriteRequired = true;
1678  }
1679  }
1680  if(makers.empty() && !flacStream) {
1681  // an ID3v2 tag is not written and it is not a FLAC stream
1682  // -> can't include padding
1683  if(padding) {
1684  // but padding would be present -> need to rewrite
1685  padding = 0; // can't write the preferred padding despite rewriting
1686  rewriteRequired = true;
1687  }
1688  } else if(rewriteRequired) {
1689  // rewriting is forced or new ID3v2 tag is too big for available space
1690  // -> use preferred padding when rewriting anyways
1691  padding = preferredPadding();
1692  } else if(makers.empty() && flacStream && padding && padding < 4) {
1693  // no ID3v2 tag -> must include padding in FLAC stream
1694  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1695  padding = preferredPadding();
1696  rewriteRequired = true;
1697  }
1698  if(rewriteRequired && flacStream && makers.empty() && padding) {
1699  // the first 4 byte of FLAC padding actually don't count because these
1700  // can not be used for additional meta data
1701  padding += 4;
1702  }
1703  updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1704 
1705  // setup stream(s) for writing
1706  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1707  string backupPath;
1708  NativeFileStream &outputStream = stream();
1709  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1710 
1711  if(rewriteRequired) {
1712  if(m_saveFilePath.empty()) {
1713  // move current file to temp dir and reopen it as backupStream, recreate original file
1714  try {
1715  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1716  // recreate original file, define buffer variables
1717  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1718  } catch(...) {
1719  const char *what = catchIoFailure();
1720  addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1721  throwIoFailure(what);
1722  }
1723  } else {
1724  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1725  try {
1726  close();
1727  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1728  backupStream.open(path(), ios_base::in | ios_base::binary);
1729  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1730  } catch(...) {
1731  const char *what = catchIoFailure();
1732  addNotification(NotificationType::Critical, "Opening streams to write output file failed.", context);
1733  throwIoFailure(what);
1734  }
1735  }
1736 
1737  } else { // !rewriteRequired
1738  // reopen original file to ensure it is opened for writing
1739  try {
1740  close();
1741  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1742  } catch(...) {
1743  const char *what = catchIoFailure();
1744  addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
1745  throwIoFailure(what);
1746  }
1747  }
1748 
1749  // start actual writing
1750  try {
1751  if(!makers.empty()) {
1752  // write ID3v2 tags
1753  updateStatus("Writing ID3v2 tag ...");
1754  for(auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1755  i->make(outputStream, 0);
1756  }
1757  // include padding into the last ID3v2 tag
1758  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1759  }
1760 
1761  if(flacStream) {
1762  if(padding && startOfLastMetaDataBlock) {
1763  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1764  flacMetaData.seekg(startOfLastMetaDataBlock);
1765  flacMetaData.seekp(startOfLastMetaDataBlock);
1766  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1767  flacMetaData.seekg(0);
1768  }
1769 
1770  // write FLAC metadata
1771  outputStream << flacMetaData.rdbuf();
1772 
1773  // write padding
1774  if(padding) {
1775  flacStream->makePadding(outputStream, padding, true);
1776  }
1777  }
1778 
1779  if(makers.empty() && !flacStream){
1780  // just write padding (however, padding should be set to 0 in this case?)
1781  for(; padding; --padding) {
1782  outputStream.put(0);
1783  }
1784  }
1785 
1786  // copy / skip actual stream data
1787  // -> determine media data size
1788  uint64 mediaDataSize = size() - streamOffset;
1789  if(m_actualExistingId3v1Tag) {
1790  mediaDataSize -= 128;
1791  }
1792 
1793  if(rewriteRequired) {
1794  // copy data from original file
1795  switch(m_containerFormat) {
1797  updateStatus("Writing MPEG audio frames ...");
1798  break;
1799  default:
1800  updateStatus("Writing frames ...");
1801  }
1802  backupStream.seekg(streamOffset);
1803  CopyHelper<0x4000> copyHelper;
1804  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
1805  updatePercentage(100.0);
1806  } else {
1807  // just skip actual stream data
1808  outputStream.seekp(mediaDataSize, ios_base::cur);
1809  }
1810 
1811  // write ID3v1 tag
1812  if(m_id3v1Tag) {
1813  updateStatus("Writing ID3v1 tag ...");
1814  try {
1815  m_id3v1Tag->make(stream());
1816  } catch(const Failure &) {
1817  addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
1818  }
1819  }
1820 
1821  // handle streams
1822  if(rewriteRequired) {
1823  // report new size
1824  reportSizeChanged(outputStream.tellp());
1825  // "save as path" is now the regular path
1826  if(!saveFilePath().empty()) {
1828  m_saveFilePath.clear();
1829  }
1830  // stream is useless for further usage because it is write-only
1831  outputStream.close();
1832  } else {
1833  const auto newSize = static_cast<uint64>(outputStream.tellp());
1834  if(newSize < size()) {
1835  // file is smaller after the modification -> truncate
1836  // -> close stream before truncating
1837  outputStream.close();
1838  // -> truncate file
1839  if(truncate(path().c_str(), newSize) == 0) {
1840  reportSizeChanged(newSize);
1841  } else {
1842  addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
1843  }
1844  } else {
1845  // file is longer after the modification -> just report new size
1846  reportSizeChanged(newSize);
1847  }
1848  }
1849 
1850  } catch(...) {
1851  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, context);
1852  }
1853  }
1854 }
1855 
1856 }
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 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 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: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.