Tag Parser  7.1.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 "./abstracttrack.h"
3 #include "./backuphelper.h"
4 #include "./diagnostics.h"
5 #include "./exceptions.h"
6 #include "./progressfeedback.h"
7 #include "./signature.h"
8 #include "./tag.h"
9 
10 #include "./id3/id3v1tag.h"
11 #include "./id3/id3v2tag.h"
12 
13 #include "./wav/waveaudiostream.h"
14 
16 
17 #include "./adts/adtsstream.h"
18 
19 #include "./mp4/mp4atom.h"
20 #include "./mp4/mp4container.h"
21 #include "./mp4/mp4ids.h"
22 #include "./mp4/mp4tag.h"
23 #include "./mp4/mp4track.h"
24 
25 #include "./matroska/ebmlelement.h"
27 #include "./matroska/matroskatag.h"
29 
30 #include "./ogg/oggcontainer.h"
31 
32 #include "./flac/flacmetadata.h"
33 #include "./flac/flacstream.h"
34 
35 #include <c++utilities/chrono/timespan.h>
36 #include <c++utilities/conversion/stringconversion.h>
37 #include <c++utilities/io/catchiofailure.h>
38 
39 #include <unistd.h>
40 
41 #include <algorithm>
42 #include <cstdio>
43 #include <functional>
44 #include <iomanip>
45 #include <ios>
46 #include <memory>
47 #include <system_error>
48 
49 using namespace std;
50 using namespace std::placeholders;
51 using namespace IoUtilities;
52 using namespace ConversionUtilities;
53 using namespace ChronoUtilities;
54 
60 namespace TagParser {
61 
62 #ifdef FORCE_FULL_PARSE_DEFAULT
63 #define MEDIAINFO_CPP_FORCE_FULL_PARSE true
64 #else
65 #define MEDIAINFO_CPP_FORCE_FULL_PARSE false
66 #endif
67 
81 MediaFileInfo::MediaFileInfo()
82  : m_containerParsingStatus(ParsingStatus::NotParsedYet)
83  , m_containerFormat(ContainerFormat::Unknown)
84  , m_containerOffset(0)
85  , m_actualExistingId3v1Tag(false)
86  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
87  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
88  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
89  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
90  , m_minPadding(0)
91  , m_maxPadding(0)
92  , m_preferredPadding(0)
93  , m_tagPosition(ElementPosition::BeforeData)
94  , m_indexPosition(ElementPosition::BeforeData)
95  , m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
96  , m_forceRewrite(true)
97  , m_forceTagPosition(true)
98  , m_forceIndexPosition(true)
99 {
100 }
101 
107 MediaFileInfo::MediaFileInfo(const string &path)
108  : BasicFileInfo(path)
109  , m_containerParsingStatus(ParsingStatus::NotParsedYet)
110  , m_containerFormat(ContainerFormat::Unknown)
111  , m_containerOffset(0)
112  , m_actualExistingId3v1Tag(false)
113  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
114  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
115  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
116  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
117  , m_minPadding(0)
118  , m_maxPadding(0)
119  , m_preferredPadding(0)
120  , m_tagPosition(ElementPosition::BeforeData)
121  , m_indexPosition(ElementPosition::BeforeData)
122  , m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
123  , m_forceRewrite(true)
124  , m_forceTagPosition(true)
125  , m_forceIndexPosition(true)
126 {
127 }
128 
133 {
134 }
135 
152 {
153  // skip if container format already parsed
155  return;
156  }
157 
158  static const string context("parsing file header");
159  open(); // ensure the file is open
160  m_containerFormat = ContainerFormat::Unknown;
161 
162  // file size
163  m_paddingSize = 0;
164  m_containerOffset = 0;
165 
166  // read signatrue
167  char buff[16];
168  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
169 startParsingSignature:
170  if (size() - containerOffset() >= 16) {
171  stream().seekg(m_containerOffset, ios_base::beg);
172  stream().read(buff, sizeof(buff));
173 
174  // skip zero bytes/padding
175  size_t bytesSkipped = 0;
176  for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
177  ;
178  if (bytesSkipped >= 4) {
179  m_containerOffset += bytesSkipped;
180 
181  // give up after 0x100 bytes
182  if ((m_paddingSize += bytesSkipped) >= 0x100u) {
183  m_containerParsingStatus = ParsingStatus::NotSupported;
184  m_containerFormat = ContainerFormat::Unknown;
185  return;
186  }
187 
188  // try again
189  goto startParsingSignature;
190  }
191  if (m_paddingSize) {
192  diag.emplace_back(DiagLevel::Warning, argsToString(m_paddingSize, " zero-bytes skipped at the beginning of the file."), context);
193  }
194 
195  // parse signature
196  switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
198  // save position of ID3v2 tag
199  m_actualId3v2TagOffsets.push_back(m_containerOffset);
200  if (m_actualId3v2TagOffsets.size() == 2) {
201  diag.emplace_back(DiagLevel::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
202  }
203 
204  // read ID3v2 header
205  stream().seekg(m_containerOffset + 5, ios_base::beg);
206  stream().read(buff, 5);
207 
208  // set the container offset to skip ID3v2 header
209  m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
210  if ((*buff) & 0x10) {
211  // footer present
212  m_containerOffset += 10;
213  }
214 
215  // continue reading signature
216  goto startParsingSignature;
217 
220  // MP4/QuickTime is handled using Mp4Container instance
221  m_container = make_unique<Mp4Container>(*this, m_containerOffset);
222  try {
223  static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
224  } catch (const Failure &) {
225  m_containerParsingStatus = ParsingStatus::CriticalFailure;
226  }
227  break;
228  }
229  case ContainerFormat::Ebml: {
230  // EBML/Matroska is handled using MatroskaContainer instance
231  auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
232  try {
233  container->parseHeader(diag);
234  if (container->documentType() == "matroska") {
235  m_containerFormat = ContainerFormat::Matroska;
236  } else if (container->documentType() == "webm") {
237  m_containerFormat = ContainerFormat::Webm;
238  }
239  if (m_forceFullParse) {
240  // validating the element structure of Matroska files takes too long when
241  // parsing big files so do this only when explicitely desired
242  container->validateElementStructure(diag, &m_paddingSize);
243  container->validateIndex(diag);
244  }
245  } catch (const Failure &) {
246  m_containerParsingStatus = ParsingStatus::CriticalFailure;
247  }
248  m_container = move(container);
249  break;
250  }
252  // Ogg is handled by OggContainer instance
253  m_container = make_unique<OggContainer>(*this, m_containerOffset);
254  static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
255  break;
257  // container format is still unknown -> check for magic numbers at odd offsets
258  // -> check for tar (magic number at offset 0x101)
259  if (size() > 0x107) {
260  stream().seekg(0x101);
261  stream().read(buff, 6);
262  if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
263  m_containerFormat = ContainerFormat::Tar;
264  break;
265  }
266  }
267  break;
268  default:;
269  }
270  }
271 
272  // set parsing status
273  if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
274  if (m_containerFormat == ContainerFormat::Unknown) {
275  m_containerParsingStatus = ParsingStatus::NotSupported;
276  } else {
277  m_containerParsingStatus = ParsingStatus::Ok;
278  }
279  }
280 }
281 
296 {
297  // skip if tracks already parsed
299  return;
300  }
301  static const string context("parsing tracks");
302 
303  try {
304  // parse tracks via container object
305  if (m_container) {
306  m_container->parseTracks(diag);
307  m_tracksParsingStatus = ParsingStatus::Ok;
308  return;
309  }
310 
311  // parse tracks via track object for "single-track"-formats
312  switch (m_containerFormat) {
314  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
315  break;
317  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
318  break;
320  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
321  break;
323  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
324  break;
325  default:
326  throw NotImplementedException();
327  }
328  m_singleTrack->parseHeader(diag);
329 
330  // take padding for some "single-track" formats into account
331  switch (m_containerFormat) {
333  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
334  break;
335  default:;
336  }
337 
338  m_tracksParsingStatus = ParsingStatus::Ok;
339 
340  } catch (const NotImplementedException &) {
341  diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
342  m_tracksParsingStatus = ParsingStatus::NotSupported;
343  } catch (const Failure &) {
344  diag.emplace_back(DiagLevel::Critical, "Unable to parse tracks.", context);
345  m_tracksParsingStatus = ParsingStatus::CriticalFailure;
346  }
347 }
348 
364 {
365  // skip if tags already parsed
367  return;
368  }
369  static const string context("parsing tag");
370 
371  // check for ID3v1 tag
372  if (size() >= 128) {
373  m_id3v1Tag = make_unique<Id3v1Tag>();
374  try {
375  stream().seekg(-128, ios_base::end);
376  m_id3v1Tag->parse(stream(), diag);
377  m_actualExistingId3v1Tag = true;
378  } catch (const NoDataFoundException &) {
379  m_id3v1Tag.reset();
380  } catch (const Failure &) {
381  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
382  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
383  }
384  }
385 
386  // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
387  m_id3v2Tags.clear();
388  for (const auto offset : m_actualId3v2TagOffsets) {
389  auto id3v2Tag = make_unique<Id3v2Tag>();
390  stream().seekg(offset, ios_base::beg);
391  try {
392  id3v2Tag->parse(stream(), size() - static_cast<uint64>(offset), diag);
393  m_paddingSize += id3v2Tag->paddingSize();
394  } catch (const NoDataFoundException &) {
395  continue;
396  } catch (const Failure &) {
397  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
398  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v2 tag.", context);
399  }
400  m_id3v2Tags.emplace_back(id3v2Tag.release());
401  }
402 
403  // check for tags in tracks (FLAC only) or via container object
404  try {
405  if (m_containerFormat == ContainerFormat::Flac) {
406  parseTracks(diag);
407  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
408  m_tagsParsingStatus = m_tracksParsingStatus;
409  }
410  return;
411  } else if (m_container) {
412  m_container->parseTags(diag);
413  } else {
414  throw NotImplementedException();
415  }
416 
417  // set status, but do not override error/unsupported status form ID3 tags here
418  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
419  m_tagsParsingStatus = ParsingStatus::Ok;
420  }
421 
422  } catch (const NotImplementedException &) {
423  // set status to not supported, but do not override parsing status from ID3 tags here
424  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
425  m_tagsParsingStatus = ParsingStatus::NotSupported;
426  }
427  diag.emplace_back(DiagLevel::Information, "Parsing tags is not implemented for the container format of the file.", context);
428  } catch (const Failure &) {
429  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
430  diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
431  }
432 }
433 
446 {
447  // skip if chapters already parsed
449  return;
450  }
451  static const string context("parsing chapters");
452 
453  try {
454  // parse chapters via container object
455  if (!m_container) {
456  throw NotImplementedException();
457  }
458  m_container->parseChapters(diag);
459  m_chaptersParsingStatus = ParsingStatus::Ok;
460  } catch (const NotImplementedException &) {
461  m_chaptersParsingStatus = ParsingStatus::NotSupported;
462  diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
463  } catch (const Failure &) {
464  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
465  diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
466  }
467 }
468 
481 {
482  // skip if attachments already parsed
484  return;
485  }
486  static const string context("parsing attachments");
487 
488  try {
489  // parse attachments via container object
490  if (!m_container) {
491  throw NotImplementedException();
492  }
493  m_container->parseAttachments(diag);
494  m_attachmentsParsingStatus = ParsingStatus::Ok;
495  } catch (const NotImplementedException &) {
496  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
497  diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
498  } catch (const Failure &) {
499  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
500  diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
501  }
502 }
503 
511 {
512  parseContainerFormat(diag);
513  parseTracks(diag);
514  parseTags(diag);
515  parseChapters(diag);
516  parseAttachments(diag);
517 }
518 
531 {
532  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
534  return false;
535  }
536 
537  // check if tags need to be created/adjusted/removed
538  const auto requiredTargets(settings.requiredTargets);
539  const auto flags(settings.flags);
540  const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
541  auto targetsSupported = false;
542  if (areTagsSupported() && m_container) {
543  // container object takes care of tag management
544  if (targetsRequired) {
545  // check whether container supports targets
546  if (m_container->tagCount()) {
547  // all tags in the container should support targets if the first one supports targets
548  targetsSupported = m_container->tag(0)->supportsTarget();
549  } else {
550  // try to create a new tag and check whether targets are supported
551  auto *const tag = m_container->createTag();
552  if (tag && (targetsSupported = tag->supportsTarget())) {
553  tag->setTarget(requiredTargets.front());
554  }
555  }
556  if (targetsSupported) {
557  for (const auto &target : requiredTargets) {
558  m_container->createTag(target);
559  }
560  }
561  } else {
562  // no targets are required -> just ensure that at least one tag is present
563  m_container->createTag();
564  }
565  return true;
566  }
567 
568  // no container object present
569  switch (m_containerFormat) {
571  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
572  break;
573  default:
574  // create ID3 tag(s)
576  switch (containerFormat()) {
580  break;
581  default:
582  return false;
583  }
584  }
585  // create ID3 tags according to id3v2usage and id3v2usage
586  // always create ID3v1 tag -> ensure there is one
587  if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
588  auto *const id3v1Tag = createId3v1Tag();
589  if (flags & TagCreationFlags::Id3InitOnCreate) {
590  for (const auto &id3v2Tag : id3v2Tags()) {
591  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
592  id3v1Tag->insertValues(*id3v2Tag, true);
593  // ID3v1 does not support all text encodings which might be used in ID3v2
595  }
596  }
597  }
598  if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
599  // always create ID3v2 tag -> ensure there is one and set version
600  auto *const id3v2Tag = createId3v2Tag();
601  id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
602  if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
603  id3v2Tag->insertValues(*id3v1Tag(), true);
604  }
605  }
606  }
607 
609  mergeId3v2Tags();
610  }
611  // remove ID3 tags according to settings
612  if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
613  // transfer tags to ID3v2 tag before removing
615  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
616  }
617  removeId3v1Tag();
618  }
619  if (settings.id3v2usage == TagUsage::Never) {
621  // transfer tags to ID3v1 tag before removing
622  for (const auto &tag : id3v2Tags()) {
623  id3v1Tag()->insertValues(*tag, false);
624  }
625  }
627  } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
628  // set version of ID3v2 tag according user preferences
629  for (const auto &tag : id3v2Tags()) {
630  tag->setVersion(settings.id3v2MajorVersion, 0);
631  }
632  }
633  return true;
634 }
635 
657 {
658  static const string context("making file");
659  diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
660  bool previousParsingSuccessful = true;
661  switch (tagsParsingStatus()) {
662  case ParsingStatus::Ok:
664  break;
665  default:
666  previousParsingSuccessful = false;
667  diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
668  }
669  switch (tracksParsingStatus()) {
670  case ParsingStatus::Ok:
672  break;
673  default:
674  previousParsingSuccessful = false;
675  diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
676  }
677  if (!previousParsingSuccessful) {
678  throw InvalidDataException();
679  }
680  if (m_container) { // container object takes care
681  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
682  if (hasId3v1Tag()) {
683  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
684  }
685  if (hasId3v2Tag()) {
686  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
687  }
688  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
689  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
690  try {
691  m_container->makeFile(diag, progress);
692  } catch (...) {
693  // since the file might be messed up, invalidate the parsing results
695  throw;
696  }
697  } else { // implementation if no container object is present
698  // assume the file is a MP3 file
699  try {
700  makeMp3File(diag, progress);
701  } catch (...) {
702  // since the file might be messed up, invalidate the parsing results
704  throw;
705  }
706  }
708 }
709 
723 {
724  MediaType mediaType = MediaType::Unknown;
725  unsigned int version = 0;
726  switch (m_containerFormat) {
727  case ContainerFormat::Ogg: {
728  // check for video track or whether only Opus or Speex tracks are present
729  const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
730  if (tracks.empty()) {
731  break;
732  }
733  bool onlyOpus = true, onlySpeex = true;
734  for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
735  if (track->mediaType() == MediaType::Video) {
736  mediaType = MediaType::Video;
737  }
738  if (track->format().general != GeneralMediaFormat::Opus) {
739  onlyOpus = false;
740  }
741  if (track->format().general != GeneralMediaFormat::Speex) {
742  onlySpeex = false;
743  }
744  }
745  if (onlyOpus) {
746  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
747  } else if (onlySpeex) {
748  version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
749  }
750  break;
751  }
755  break;
757  if (m_singleTrack) {
758  version = m_singleTrack->format().sub;
759  }
760  break;
761  default:;
762  }
763  return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
764 }
765 
776 const char *MediaFileInfo::mimeType() const
777 {
778  MediaType mediaType;
779  switch (m_containerFormat) {
784  break;
785  default:
786  mediaType = MediaType::Unknown;
787  }
788  return TagParser::containerMimeType(m_containerFormat, mediaType);
789 }
790 
803 vector<AbstractTrack *> MediaFileInfo::tracks() const
804 {
805  vector<AbstractTrack *> res;
806  size_t trackCount = 0;
807  size_t containerTrackCount = 0;
808  if (m_singleTrack) {
809  trackCount = 1;
810  }
811  if (m_container) {
812  trackCount += (containerTrackCount = m_container->trackCount());
813  }
814  res.reserve(trackCount);
815 
816  if (m_singleTrack) {
817  res.push_back(m_singleTrack.get());
818  }
819  for (size_t i = 0; i != containerTrackCount; ++i) {
820  res.push_back(m_container->track(i));
821  }
822  return res;
823 }
824 
834 {
836  return false;
837  }
838  if (m_singleTrack && m_singleTrack->mediaType() == type) {
839  return true;
840  } else if (m_container) {
841  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
842  if (m_container->track(i)->mediaType() == type) {
843  return true;
844  }
845  }
846  }
847  return false;
848 }
849 
860 {
861  if (m_container) {
862  return m_container->duration();
863  } else if (m_singleTrack) {
864  return m_singleTrack->duration();
865  }
866  return TimeSpan();
867 }
868 
879 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
880 {
881  unordered_set<string> res;
882  if (m_container) {
883  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
884  const AbstractTrack *track = m_container->track(i);
885  if ((type == MediaType::Unknown || track->mediaType() == type) && !track->language().empty() && track->language() != "und") {
886  res.emplace(track->language());
887  }
888  }
889  } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
890  && m_singleTrack->language() != "und") {
891  res.emplace(m_singleTrack->language());
892  }
893  return res;
894 }
895 
908 {
909  if (m_container) {
910  const size_t trackCount = m_container->trackCount();
911  vector<string> parts;
912  parts.reserve(trackCount);
913  for (size_t i = 0; i != trackCount; ++i) {
914  const string description(m_container->track(i)->description());
915  if (!description.empty()) {
916  parts.emplace_back(move(description));
917  }
918  }
919  return joinStrings(parts, " / ");
920  } else if (m_singleTrack) {
921  return m_singleTrack->description();
922  }
923  return string();
924 }
925 
936 {
938  return false;
939  }
940  if (m_id3v1Tag) {
941  m_id3v1Tag.reset();
942  return true;
943  }
944  return false;
945 }
946 
963 {
965  return nullptr;
966  }
967  if (!m_id3v1Tag) {
968  m_id3v1Tag = make_unique<Id3v1Tag>();
969  }
970  return m_id3v1Tag.get();
971 }
972 
984 {
986  return false;
987  }
988  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
989  if (i->get() == tag) {
990  m_id3v2Tags.erase(i);
991  return true;
992  }
993  }
994  return false;
995 }
996 
1006 {
1007  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1008  return false;
1009  }
1010  m_id3v2Tags.clear();
1011  return true;
1012 }
1013 
1030 {
1031  if (m_id3v2Tags.empty()) {
1032  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1033  }
1034  return m_id3v2Tags.front().get();
1035 }
1036 
1052 {
1053  if (!tag) {
1054  return;
1055  }
1056 
1057  // remove tag via container
1058  if (m_container) {
1059  m_container->removeTag(tag);
1060  return;
1061  }
1062 
1063  // remove tag via track for "single-track" formats
1064  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1065  auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1066  if (flacStream->vorbisComment() == tag) {
1067  flacStream->removeVorbisComment();
1068  return;
1069  }
1070  }
1071 
1072  // remove ID3 tags
1073  if (m_id3v1Tag.get() == tag) {
1074  m_id3v1Tag.reset();
1075  return;
1076  }
1077  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1078  if (i->get() == tag) {
1079  m_id3v2Tags.erase(i);
1080  break;
1081  }
1082  }
1083 }
1084 
1092 {
1093  if (m_container) {
1094  m_container->removeAllTags();
1095  }
1096  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1097  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1098  }
1099  m_id3v1Tag.reset();
1100  m_id3v2Tags.clear();
1101 }
1102 
1107 {
1108  if (m_container && m_container->chapterCount()) {
1109  return true;
1110  }
1111  switch (m_containerFormat) {
1113  case ContainerFormat::Webm:
1114  return true;
1115  default:
1116  return false;
1117  }
1118 }
1119 
1124 {
1125  if (m_container && m_container->attachmentCount()) {
1126  return true;
1127  }
1128  switch (m_containerFormat) {
1130  case ContainerFormat::Webm:
1131  return true;
1132  default:
1133  return false;
1134  }
1135 }
1136 
1141 {
1142  if (trackCount()) {
1143  return true;
1144  }
1145  switch (m_containerFormat) {
1146  case ContainerFormat::Mp4:
1149  case ContainerFormat::Ogg:
1151  case ContainerFormat::Webm:
1152  return true;
1153  default:
1154  return false;
1155  }
1156 }
1157 
1162 {
1163  switch (m_containerFormat) {
1164  case ContainerFormat::Adts:
1165  case ContainerFormat::Flac:
1168  case ContainerFormat::Mp4:
1169  case ContainerFormat::Ogg:
1171  case ContainerFormat::Webm:
1172  // these container formats are supported
1173  return true;
1174  default:
1175  // the container format is unsupported
1176  // -> an ID3 tag might be already present, in this case the tags are considered supported
1177  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1178  }
1179 }
1180 
1187 {
1188  // simply return the first tag here since MP4 files never contain multiple tags
1189  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1190  && m_container->tagCount() > 0
1191  ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1192  : nullptr;
1193 }
1194 
1201 const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1202 {
1203  // matroska files might contain multiple tags (targeting different scopes)
1204  if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1205  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1206  } else {
1207  static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1208  return empty;
1209  }
1210 }
1211 
1218 {
1219  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1220  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1221  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1222 }
1223 
1229 vector<AbstractChapter *> MediaFileInfo::chapters() const
1230 {
1231  vector<AbstractChapter *> res;
1232  if (m_container) {
1233  const size_t count = m_container->chapterCount();
1234  res.reserve(count);
1235  for (size_t i = 0; i != count; ++i) {
1236  res.push_back(m_container->chapter(i));
1237  }
1238  }
1239  return res;
1240 }
1241 
1247 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1248 {
1249  vector<AbstractAttachment *> res;
1250  if (m_container) {
1251  const size_t count = m_container->attachmentCount();
1252  res.reserve(count);
1253  for (size_t i = 0; i != count; ++i) {
1254  res.push_back(m_container->attachment(i));
1255  }
1256  }
1257  return res;
1258 }
1259 
1272 {
1273  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1274  m_containerFormat = ContainerFormat::Unknown;
1275  m_containerOffset = 0;
1276  m_paddingSize = 0;
1277  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1278  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1279  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1280  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1281  m_id3v1Tag.reset();
1282  m_id3v2Tags.clear();
1283  m_actualId3v2TagOffsets.clear();
1284  m_actualExistingId3v1Tag = false;
1285  m_container.reset();
1286  m_singleTrack.reset();
1287 }
1288 
1306 {
1307  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1308  if (begin == end) {
1309  return;
1310  }
1311  Id3v2Tag &first = **begin;
1312  auto isecond = begin + 1;
1313  if (isecond == end) {
1314  return;
1315  }
1316  for (auto i = isecond; i != end; ++i) {
1317  first.insertFields(**i, false);
1318  }
1319  m_id3v2Tags.erase(isecond, end - 1);
1320 }
1321 
1333 {
1335  return false;
1336  }
1339 }
1340 
1352 {
1354  return false;
1355  }
1358 }
1359 
1375 {
1376  switch (m_containerFormat) {
1377  case ContainerFormat::Ogg:
1378  if (m_container) {
1379  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1380  }
1381  break;
1382  case ContainerFormat::Flac:
1383  if (m_singleTrack) {
1384  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1385  }
1386  break;
1387  default:;
1388  }
1389  return nullptr;
1390 }
1391 
1402 {
1403  switch (m_containerFormat) {
1404  case ContainerFormat::Ogg:
1405  if (m_container) {
1406  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1407  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1408  return hadTags;
1409  }
1410  break;
1411  case ContainerFormat::Flac:
1412  if (m_singleTrack) {
1413  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1414  }
1415  break;
1416  default:;
1417  }
1418  return false;
1419 }
1420 
1429 void MediaFileInfo::tags(vector<Tag *> &tags) const
1430 {
1431  if (hasId3v1Tag()) {
1432  tags.push_back(m_id3v1Tag.get());
1433  }
1434  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1435  tags.push_back(tag.get());
1436  }
1437  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1438  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1439  tags.push_back(vorbisComment);
1440  }
1441  }
1442  if (m_container) {
1443  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1444  tags.push_back(m_container->tag(i));
1445  }
1446  }
1447 }
1448 
1453 {
1454  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1455  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1456 }
1457 
1464 vector<Tag *> MediaFileInfo::tags() const
1465 {
1466  vector<Tag *> res;
1467  tags(res);
1468  return res;
1469 }
1470 
1475 {
1478 }
1479 
1483 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1484 {
1485  static const string context("making MP3/FLAC file");
1486 
1487  // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1488  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1489  && m_containerFormat != ContainerFormat::Flac) {
1490  // alter ID3v1 tag
1491  if (!m_id3v1Tag) {
1492  // remove ID3v1 tag
1493  if (!m_actualExistingId3v1Tag) {
1494  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1495  return;
1496  }
1497  progress.updateStep("Removing ID3v1 tag ...");
1498  stream().close();
1499  if (truncate(path().data(), static_cast<std::streamoff>(size() - 128)) == 0) {
1500  reportSizeChanged(size() - 128);
1501  } else {
1502  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1503  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1504  }
1505  return;
1506  } else {
1507  // add or update ID3v1 tag
1508  if (m_actualExistingId3v1Tag) {
1509  progress.updateStep("Updating existing ID3v1 tag ...");
1510  // ensure the file is still open / not readonly
1511  open();
1512  stream().seekp(-128, ios_base::end);
1513  try {
1514  m_id3v1Tag->make(stream(), diag);
1515  } catch (const Failure &) {
1516  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1517  }
1518  } else {
1519  progress.updateStep("Adding new ID3v1 tag ...");
1520  // ensure the file is still open / not readonly
1521  open();
1522  stream().seekp(0, ios_base::end);
1523  try {
1524  m_id3v1Tag->make(stream(), diag);
1525  } catch (const Failure &) {
1526  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1527  }
1528  }
1529  }
1530  return;
1531  }
1532 
1533  // ID3v2 needs to be modified
1534  FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1535  progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1536 
1537  // prepare ID3v2 tags
1538  vector<Id3v2TagMaker> makers;
1539  makers.reserve(m_id3v2Tags.size());
1540  uint32 tagsSize = 0;
1541  for (auto &tag : m_id3v2Tags) {
1542  try {
1543  makers.emplace_back(tag->prepareMaking(diag));
1544  tagsSize += makers.back().requiredSize();
1545  } catch (const Failure &) {
1546  }
1547  }
1548 
1549  // determine stream offset and make track/format specific metadata
1550  uint32 streamOffset; // where the actual stream starts
1551  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1552  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1553  uint32 startOfLastMetaDataBlock;
1554  if (flacStream) {
1555  // if it is a raw FLAC stream, make FLAC metadata
1556  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1557  tagsSize += static_cast<uint32>(flacMetaData.tellp());
1558  streamOffset = flacStream->streamOffset();
1559  } else {
1560  // make no further metadata, just use the container offset as stream offset
1561  streamOffset = static_cast<uint32>(m_containerOffset);
1562  }
1563 
1564  // check whether rewrite is required
1565  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1566  size_t padding = 0;
1567  if (!rewriteRequired) {
1568  // rewriting is not forced and new tag is not too big for available space
1569  // -> calculate new padding
1570  padding = streamOffset - tagsSize;
1571  // -> check whether the new padding matches specifications
1572  if (padding < minPadding() || padding > maxPadding()) {
1573  rewriteRequired = true;
1574  }
1575  }
1576  if (makers.empty() && !flacStream) {
1577  // an ID3v2 tag is not written and it is not a FLAC stream
1578  // -> can't include padding
1579  if (padding) {
1580  // but padding would be present -> need to rewrite
1581  padding = 0; // can't write the preferred padding despite rewriting
1582  rewriteRequired = true;
1583  }
1584  } else if (rewriteRequired) {
1585  // rewriting is forced or new ID3v2 tag is too big for available space
1586  // -> use preferred padding when rewriting anyways
1587  padding = preferredPadding();
1588  } else if (makers.empty() && flacStream && padding && padding < 4) {
1589  // no ID3v2 tag -> must include padding in FLAC stream
1590  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1591  padding = preferredPadding();
1592  rewriteRequired = true;
1593  }
1594  if (rewriteRequired && flacStream && makers.empty() && padding) {
1595  // the first 4 byte of FLAC padding actually don't count because these
1596  // can not be used for additional meta data
1597  padding += 4;
1598  }
1599  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1600 
1601  // setup stream(s) for writing
1602  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1603  string backupPath;
1604  NativeFileStream &outputStream = stream();
1605  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1606 
1607  if (rewriteRequired) {
1608  if (m_saveFilePath.empty()) {
1609  // move current file to temp dir and reopen it as backupStream, recreate original file
1610  try {
1611  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1612  // recreate original file, define buffer variables
1613  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1614  } catch (...) {
1615  const char *const what = catchIoFailure();
1616  diag.emplace_back(DiagLevel::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1617  throwIoFailure(what);
1618  }
1619  } else {
1620  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1621  try {
1622  close();
1623  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1624  backupStream.open(path(), ios_base::in | ios_base::binary);
1625  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1626  } catch (...) {
1627  const char *const what = catchIoFailure();
1628  diag.emplace_back(DiagLevel::Critical, "Opening streams to write output file failed.", context);
1629  throwIoFailure(what);
1630  }
1631  }
1632 
1633  } else { // !rewriteRequired
1634  // reopen original file to ensure it is opened for writing
1635  try {
1636  close();
1637  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1638  } catch (...) {
1639  const char *const what = catchIoFailure();
1640  diag.emplace_back(DiagLevel::Critical, "Opening the file with write permissions failed.", context);
1641  throwIoFailure(what);
1642  }
1643  }
1644 
1645  // start actual writing
1646  try {
1647  // ensure we can cast padding safely to uint32
1648  if (padding > numeric_limits<uint32>::max()) {
1649  padding = numeric_limits<uint32>::max();
1650  diag.emplace_back(
1651  DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1652  }
1653 
1654  if (!makers.empty()) {
1655  // write ID3v2 tags
1656  progress.updateStep("Writing ID3v2 tag ...");
1657  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1658  i->make(outputStream, 0, diag);
1659  }
1660  // include padding into the last ID3v2 tag
1661  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<uint32>(padding), diag);
1662  }
1663 
1664  if (flacStream) {
1665  if (padding && startOfLastMetaDataBlock) {
1666  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1667  flacMetaData.seekg(static_cast<streamoff>(startOfLastMetaDataBlock));
1668  flacMetaData.seekp(static_cast<streamoff>(startOfLastMetaDataBlock));
1669  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1670  flacMetaData.seekg(0);
1671  }
1672 
1673  // write FLAC metadata
1674  outputStream << flacMetaData.rdbuf();
1675 
1676  // write padding
1677  if (padding) {
1678  flacStream->makePadding(outputStream, static_cast<uint32>(padding), true, diag);
1679  }
1680  }
1681 
1682  if (makers.empty() && !flacStream) {
1683  // just write padding (however, padding should be set to 0 in this case?)
1684  for (; padding; --padding) {
1685  outputStream.put(0);
1686  }
1687  }
1688 
1689  // copy / skip actual stream data
1690  // -> determine media data size
1691  uint64 mediaDataSize = size() - streamOffset;
1692  if (m_actualExistingId3v1Tag) {
1693  mediaDataSize -= 128;
1694  }
1695 
1696  if (rewriteRequired) {
1697  // copy data from original file
1698  switch (m_containerFormat) {
1700  progress.updateStep("Writing MPEG audio frames ...");
1701  break;
1702  default:
1703  progress.updateStep("Writing frames ...");
1704  }
1705  backupStream.seekg(static_cast<streamoff>(streamOffset));
1706  CopyHelper<0x4000> copyHelper;
1707  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1708  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1709  } else {
1710  // just skip actual stream data
1711  outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1712  }
1713 
1714  // write ID3v1 tag
1715  if (m_id3v1Tag) {
1716  progress.updateStep("Writing ID3v1 tag ...");
1717  try {
1718  m_id3v1Tag->make(stream(), diag);
1719  } catch (const Failure &) {
1720  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1721  }
1722  }
1723 
1724  // handle streams
1725  if (rewriteRequired) {
1726  // report new size
1727  reportSizeChanged(static_cast<uint64>(outputStream.tellp()));
1728  // "save as path" is now the regular path
1729  if (!saveFilePath().empty()) {
1731  m_saveFilePath.clear();
1732  }
1733  // stream is useless for further usage because it is write-only
1734  outputStream.close();
1735  } else {
1736  const auto newSize = static_cast<uint64>(outputStream.tellp());
1737  if (newSize < size()) {
1738  // file is smaller after the modification -> truncate
1739  // -> close stream before truncating
1740  outputStream.close();
1741  // -> truncate file
1742  if (truncate(path().c_str(), static_cast<streamoff>(newSize)) == 0) {
1743  reportSizeChanged(newSize);
1744  } else {
1745  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1746  }
1747  } else {
1748  // file is longer after the modification -> just report new size
1749  reportSizeChanged(newSize);
1750  }
1751  }
1752 
1753  } catch (...) {
1754  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1755  }
1756 }
1757 
1758 } // namespace TagParser
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
const char * containerFormatAbbreviation() const
Returns the abbreviation of the container format as C-style string.
void parseEverything(Diagnostics &diag)
Parses the container format, the tracks and the tag information of the current file.
This exception is thrown when the an operation is invoked that has not been implemented yet...
Definition: exceptions.h:53
void open(bool readOnly=false)
Opens a std::fstream for the current file.
TAG_PARSER_EXPORT const char * description()
Implementation of TagParser::Tag for the MP4 container.
Definition: mp4tag.h:97
size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
const std::vector< std::unique_ptr< MatroskaTag > > & matroskaTags() const
Returns pointers to the assigned Matroska tags.
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition: id3v1tag.cpp:253
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
The Tag class is used to store, read and write tag information.
Definition: tag.h:95
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string...
void updateStepPercentage(byte stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
std::unordered_set< std::string > availableLanguages(TagParser::MediaType type=TagParser::MediaType::Audio) const
Determines the available languages for specified media type (by default MediaType::Audio).
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
void invalidated() override
Reimplemented from BasicFileInfo::invalidated().
VorbisComment * createVorbisComment()
Creates a Vorbis comment for the current file.
ParsingStatus tagsParsingStatus() const
Returns an indication whether tag information has been parsed yet.
bool hasId3v1Tag() const
Returns an indication whether an ID3v1 tag is assigned.
std::size_t trackCount() const
Returns the number of tracks that could be parsed.
TAG_PARSER_EXPORT const char * version()
bool hasAnyTag() const
Returns an indication whether a tag of any format is assigned.
const std::string & path() const
Returns the path of the current file.
Definition: basicfileinfo.h:97
ParsingStatus attachmentsParsingStatus() const
Returns whether the attachments have been parsed yet.
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
void mergeId3v2Tags()
Merges the assigned ID3v2 tags into a single ID3v2 tag.
MediaType
The MediaType enum specifies the type of media data (audio, video, text, ...).
Definition: mediaformat.h:13
void clearParsingResults()
Clears all parsing results and assigned/created/changed information such as detected container format...
std::vector< TagTarget > requiredTargets
Specifies the required targets. If targets are not supported by the container an informal notificatio...
Definition: settings.h:72
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
~MediaFileInfo() override
Destroys the MediaFileInfo.
TagCreationFlags flags
Specifies options to control the tag creation. See TagSettings::Flags.
Definition: settings.h:74
Implementation of TagParser::AbstractTrack for raw FLAC streams.
Definition: flacstream.h:14
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
Definition: settings.h:70
STL namespace.
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
uint64 paddingSize() const
Returns the padding size.
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
Definition: mp4container.h:19
const char * mimeType() const
Returns the MIME-type of the container format as C-style string.
MediaFileInfo()
Constructs a new MediaFileInfo.
ParsingStatus chaptersParsingStatus() const
Returns whether the chapters have been parsed yet.
TagUsage id3v1usage
Specifies the usage of ID3v1 when creating tags for MP3 files (has no effect when the file is no MP3 ...
Definition: settings.h:77
bool id3v1ToId3v2()
Converts an existing ID3v1 tag into an ID3v2 tag.
size_t maxPadding() const
Returns the maximum padding to be written before the data blocks when applying changes.
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:483
virtual void invalidated()
This function is called when the BasicFileInfo gets invalidated.
void applyChanges(Diagnostics &diag, AbortableProgressFeedback &progress)
Applies assigned/changed tag information to the current file.
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
ContainerFormat containerFormat() const
Returns the container format of the current file.
void parseAttachments(Diagnostics &diag)
Parses the attachments of the current file.
#define MEDIAINFO_CPP_FORCE_FULL_PARSE
byte id3v2MajorVersion
Specifies the ID3v2 version to be used in case an ID3v2 tag present or will be created. Valid values are 2, 3 and 4.
Definition: settings.h:81
ContainerFormat
Specifies the container format.
Definition: signature.h:17
bool hasId3v2Tag() const
Returns an indication whether an ID3v2 tag is assigned.
std::vector< Tag * > tags() const
Returns all tags assigned to the current file.
bool areTagsSupported() const
Returns an indication whether this library supports the tag format of the current file...
Implementation of TagParser::Tag for Vorbis comments.
Definition: vorbiscomment.h:25
void removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
VorbisComment * vorbisComment() const
Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
bool removeVorbisComment()
Removes all assigned Vorbis comment from the current file.
bool id3v2ToId3v1()
Converts the existing ID3v2 tags into an ID3v1 tag.
Implementation of TagParser::AbstractContainer for OGG files.
Definition: oggcontainer.h:126
void parseTags(Diagnostics &diag)
Parses the tag(s) of the current file.
Contains utility classes helping to read and write streams.
bool areChaptersSupported() const
Returns an indication whether this library supports parsing the chapters of the current file...
bool removeId3v2Tag(Id3v2Tag *tag)
Removes an assigned ID3v2 tag from the current file.
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
bool createAppropriateTags(const TagCreationSettings &settings=TagCreationSettings())
Ensures appropriate tags are created according the given settings.
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:18
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:21
size_t minPadding() const
Returns the minimum padding to be written before the data blocks when applying changes.
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
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
std::vector< AbstractChapter * > chapters() const
Returns all chapters assigned to the current file.
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks...
void close()
A possibly opened std::fstream will be closed.
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:37
void parseTracks(Diagnostics &diag)
Parses the tracks of the current file.
void updateStep(const std::string &step, byte stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
ParsingStatus tracksParsingStatus() const
Returns an indication whether tracks have been parsed yet.
std::string technicalSummary() const
Generates a short technical summary about the file&#39;s tracks.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
bool removeAllId3v2Tags()
Removes all assigned ID3v2 tags from the current file.
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
uint64 containerOffset() const
Returns the actual container start offset.
void removeAllTags()
Removes all assigned tags from the file.
AbstractContainer * container() const
Returns the container for the current file.
bool hasTracksOfType(TagParser::MediaType type) const
Returns an indication whether the current file has tracks of the specified type.
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
Implementation of TagParser::Tag for ID3v2 tags.
Definition: id3v2tag.h:61
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
void parseContainerFormat(Diagnostics &diag)
Parses the container format of the current file.
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
Definition: signature.cpp:100
Implementation of TagParser::Tag for ID3v1 tags.
Definition: id3v1tag.h:10
Mp4Tag * mp4Tag() const
Returns a pointer to the assigned MP4 tag or nullptr if none is assigned.
int insertFields(const FieldMapBasedTag< ImplementationType > &from, bool overwrite)
Inserts all fields from another tag of the same field type and compare function.
ParsingStatus containerParsingStatus() const
Returns an indication whether the container format has been parsed yet.
uint64 size() const
Returns size of the current file in bytes.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:79
bool removeId3v1Tag()
Removes a possibly assigned ID3v1 tag from the current file.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:39
bool areAttachmentsSupported() const
Returns an indication whether this library supports attachment format of the current file...
std::vector< AbstractAttachment * > attachments() const
Returns all attachments assigned to the current file.
bool areTracksSupported() const
Returns an indication whether this library supports parsing the tracks information of the current fil...
Id3v2Tag * createId3v2Tag()
Creates an ID3v2 tag for the current file.
void parseHeader(Diagnostics &diag)
Parses the header if not parsed yet.
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
void parseChapters(Diagnostics &diag)
Parses the chapters of the current file.
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
virtual unsigned int insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition: tag.cpp:129
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
TagUsage id3v2usage
Specifies the usage of ID3v2 when creating tags for MP3 files (has no effect when the file is no MP3 ...
Definition: settings.h:79
ElementPosition
Definition: settings.h:10
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154
ChronoUtilities::TimeSpan duration() const
Returns the overall duration of the file is known; otherwise returns a TimeSpan with zero ticks...
The BasicFileInfo class provides basic file information such as file name, extension, directory and size for a specified file.
Definition: basicfileinfo.h:13