Tag Parser  7.0.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 {
154  // there's no need to read the container format twice
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() - m_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  default:;
268  }
269  }
270 
271  // set parsing status
272  if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
273  if (m_containerFormat == ContainerFormat::Unknown) {
274  m_containerParsingStatus = ParsingStatus::NotSupported;
275  } else {
276  m_containerParsingStatus = ParsingStatus::Ok;
277  }
278  }
279 }
280 
295 {
296  if (tracksParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the tracks twice
297  return;
298  }
299  static const string context("parsing tracks");
300  try {
301  if (m_container) {
302  m_container->parseTracks(diag);
303  } else {
304  switch (m_containerFormat) {
306  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
307  break;
309  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
310  break;
312  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
313  break;
315  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
316  break;
317  default:
318  throw NotImplementedException();
319  }
320  m_singleTrack->parseHeader(diag);
321 
322  switch (m_containerFormat) {
324  // FLAC streams might container padding
325  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
326  break;
327  default:;
328  }
329  }
330  m_tracksParsingStatus = ParsingStatus::Ok;
331  } catch (const NotImplementedException &) {
332  diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
333  m_tracksParsingStatus = ParsingStatus::NotSupported;
334  } catch (const Failure &) {
335  diag.emplace_back(DiagLevel::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  stream().seekg(-128, ios_base::end);
365  m_id3v1Tag->parse(stream(), diag);
366  m_actualExistingId3v1Tag = true;
367  } catch (const NoDataFoundException &) {
368  m_id3v1Tag.reset();
369  } catch (const Failure &) {
370  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
371  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
372  }
373  }
374  // the offsets of the ID3v2 tags have already been parsed when parsing the container format
375  m_id3v2Tags.clear();
376  for (const auto offset : m_actualId3v2TagOffsets) {
377  auto id3v2Tag = make_unique<Id3v2Tag>();
378  stream().seekg(offset, ios_base::beg);
379  try {
380  id3v2Tag->parse(stream(), size() - offset, diag);
381  m_paddingSize += id3v2Tag->paddingSize();
382  } catch (const NoDataFoundException &) {
383  continue;
384  } catch (const Failure &) {
385  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
386  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v2 tag.", context);
387  }
388  m_id3v2Tags.emplace_back(id3v2Tag.release());
389  }
390  if (m_container) {
391  try {
392  m_container->parseTags(diag);
393  } catch (const NotImplementedException &) {
394  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
395  // do not override parsing status from ID3 tags here
396  m_tagsParsingStatus = ParsingStatus::NotSupported;
397  }
398  diag.emplace_back(DiagLevel::Information, "Parsing tags is not implemented for the container format of the file.", context);
399  } catch (const Failure &) {
400  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
401  diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
402  }
403  }
404  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
405  // do not override error status here
406  m_tagsParsingStatus = ParsingStatus::Ok;
407  }
408 }
409 
422 {
423  if (chaptersParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the chapters twice
424  return;
425  }
426  static const string context("parsing chapters");
427  try {
428  if (m_container) {
429  m_container->parseChapters(diag);
430  m_chaptersParsingStatus = ParsingStatus::Ok;
431  } else {
432  throw NotImplementedException();
433  }
434  } catch (const NotImplementedException &) {
435  m_chaptersParsingStatus = ParsingStatus::NotSupported;
436  diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
437  } catch (const Failure &) {
438  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
439  diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
440  }
441 }
442 
455 {
456  if (attachmentsParsingStatus() != ParsingStatus::NotParsedYet) { // there's no need to read the attachments twice
457  return;
458  }
459  static const string context("parsing attachments");
460  try {
461  if (m_container) {
462  m_container->parseAttachments(diag);
463  m_attachmentsParsingStatus = ParsingStatus::Ok;
464  } else {
465  throw NotImplementedException();
466  }
467  } catch (const NotImplementedException &) {
468  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
469  diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
470  } catch (const Failure &) {
471  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
472  diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
473  }
474 }
475 
483 {
484  parseContainerFormat(diag);
485  parseTracks(diag);
486  parseTags(diag);
487  parseChapters(diag);
488  parseAttachments(diag);
489 }
490 
503 {
504  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
506  return false;
507  }
508 
509  // check if tags need to be created/adjusted/removed
510  const auto requiredTargets(settings.requiredTargets);
511  const auto flags(settings.flags);
512  const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
513  auto targetsSupported = false;
514  if (areTagsSupported() && m_container) {
515  // container object takes care of tag management
516  if (targetsRequired) {
517  // check whether container supports targets
518  if (m_container->tagCount()) {
519  // all tags in the container should support targets if the first one supports targets
520  targetsSupported = m_container->tag(0)->supportsTarget();
521  } else {
522  // try to create a new tag and check whether targets are supported
523  auto *const tag = m_container->createTag();
524  if (tag && (targetsSupported = tag->supportsTarget())) {
525  tag->setTarget(requiredTargets.front());
526  }
527  }
528  if (targetsSupported) {
529  for (const auto &target : requiredTargets) {
530  m_container->createTag(target);
531  }
532  }
533  } else {
534  // no targets are required -> just ensure that at least one tag is present
535  m_container->createTag();
536  }
537  return true;
538  }
539 
540  // no container object present
541  switch (m_containerFormat) {
543  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
544  break;
545  default:
546  // create ID3 tag(s)
548  switch (containerFormat()) {
552  break;
553  default:
554  return false;
555  }
556  }
557  // create ID3 tags according to id3v2usage and id3v2usage
558  // always create ID3v1 tag -> ensure there is one
559  if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
560  auto *const id3v1Tag = createId3v1Tag();
561  if (flags & TagCreationFlags::Id3InitOnCreate) {
562  for (const auto &id3v2Tag : id3v2Tags()) {
563  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
564  id3v1Tag->insertValues(*id3v2Tag, true);
565  // ID3v1 does not support all text encodings which might be used in ID3v2
567  }
568  }
569  }
570  if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
571  // always create ID3v2 tag -> ensure there is one and set version
572  auto *const id3v2Tag = createId3v2Tag();
573  id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
574  if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
575  id3v2Tag->insertValues(*id3v1Tag(), true);
576  }
577  }
578  }
579 
581  mergeId3v2Tags();
582  }
583  // remove ID3 tags according to settings
584  if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
585  // transfer tags to ID3v2 tag before removing
587  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
588  }
589  removeId3v1Tag();
590  }
591  if (settings.id3v2usage == TagUsage::Never) {
593  // transfer tags to ID3v1 tag before removing
594  for (const auto &tag : id3v2Tags()) {
595  id3v1Tag()->insertValues(*tag, false);
596  }
597  }
599  } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
600  // set version of ID3v2 tag according user preferences
601  for (const auto &tag : id3v2Tags()) {
602  tag->setVersion(settings.id3v2MajorVersion, 0);
603  }
604  }
605  return true;
606 }
607 
629 {
630  static const string context("making file");
631  diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
632  bool previousParsingSuccessful = true;
633  switch (tagsParsingStatus()) {
634  case ParsingStatus::Ok:
636  break;
637  default:
638  previousParsingSuccessful = false;
639  diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
640  }
641  switch (tracksParsingStatus()) {
642  case ParsingStatus::Ok:
644  break;
645  default:
646  previousParsingSuccessful = false;
647  diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
648  }
649  if (!previousParsingSuccessful) {
650  throw InvalidDataException();
651  }
652  if (m_container) { // container object takes care
653  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
654  if (hasId3v1Tag()) {
655  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
656  }
657  if (hasId3v2Tag()) {
658  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
659  }
660  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
661  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
662  try {
663  m_container->makeFile(diag, progress);
664  } catch (...) {
665  // since the file might be messed up, invalidate the parsing results
667  throw;
668  }
669  } else { // implementation if no container object is present
670  // assume the file is a MP3 file
671  try {
672  makeMp3File(diag, progress);
673  } catch (...) {
674  // since the file might be messed up, invalidate the parsing results
676  throw;
677  }
678  }
680 }
681 
695 {
696  MediaType mediaType = MediaType::Unknown;
697  unsigned int version = 0;
698  switch (m_containerFormat) {
699  case ContainerFormat::Ogg: {
700  // check for video track or whether only Opus or Speex tracks are present
701  const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
702  if (tracks.empty()) {
703  break;
704  }
705  bool onlyOpus = true, onlySpeex = true;
706  for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
707  if (track->mediaType() == MediaType::Video) {
708  mediaType = MediaType::Video;
709  }
710  if (track->format().general != GeneralMediaFormat::Opus) {
711  onlyOpus = false;
712  }
713  if (track->format().general != GeneralMediaFormat::Speex) {
714  onlySpeex = false;
715  }
716  }
717  if (onlyOpus) {
718  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
719  } else if (onlySpeex) {
720  version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
721  }
722  break;
723  }
727  break;
729  if (m_singleTrack) {
730  version = m_singleTrack->format().sub;
731  }
732  break;
733  default:;
734  }
735  return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
736 }
737 
748 const char *MediaFileInfo::mimeType() const
749 {
750  MediaType mediaType;
751  switch (m_containerFormat) {
756  break;
757  default:
758  mediaType = MediaType::Unknown;
759  }
760  return TagParser::containerMimeType(m_containerFormat, mediaType);
761 }
762 
775 vector<AbstractTrack *> MediaFileInfo::tracks() const
776 {
777  vector<AbstractTrack *> res;
778  size_t trackCount = 0;
779  size_t containerTrackCount = 0;
780  if (m_singleTrack) {
781  trackCount = 1;
782  }
783  if (m_container) {
784  trackCount += (containerTrackCount = m_container->trackCount());
785  }
786  res.reserve(trackCount);
787 
788  if (m_singleTrack) {
789  res.push_back(m_singleTrack.get());
790  }
791  for (size_t i = 0; i != containerTrackCount; ++i) {
792  res.push_back(m_container->track(i));
793  }
794  return res;
795 }
796 
806 {
808  return false;
809  }
810  if (m_singleTrack && m_singleTrack->mediaType() == type) {
811  return true;
812  } else if (m_container) {
813  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
814  if (m_container->track(i)->mediaType() == type) {
815  return true;
816  }
817  }
818  }
819  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()
862  && m_singleTrack->language() != "und") {
863  res.emplace(m_singleTrack->language());
864  }
865  return res;
866 }
867 
880 {
881  if (m_container) {
882  const size_t trackCount = m_container->trackCount();
883  vector<string> parts;
884  parts.reserve(trackCount);
885  for (size_t i = 0; i != trackCount; ++i) {
886  const string description(m_container->track(i)->description());
887  if (!description.empty()) {
888  parts.emplace_back(move(description));
889  }
890  }
891  return joinStrings(parts, " / ");
892  } else if (m_singleTrack) {
893  return m_singleTrack->description();
894  }
895  return string();
896 }
897 
908 {
910  return false;
911  }
912  if (m_id3v1Tag) {
913  m_id3v1Tag.reset();
914  return true;
915  }
916  return false;
917 }
918 
935 {
937  return nullptr;
938  }
939  if (!m_id3v1Tag) {
940  m_id3v1Tag = make_unique<Id3v1Tag>();
941  }
942  return m_id3v1Tag.get();
943 }
944 
959 {
961  return false;
962  }
963  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
964  if (i->get() == tag) {
965  m_id3v2Tags.erase(i);
966  return true;
967  }
968  }
969  return false;
970 }
971 
981 {
982  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
983  return false;
984  }
985  m_id3v2Tags.clear();
986  return true;
987 }
988 
1005 {
1006  if (m_id3v2Tags.empty()) {
1007  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1008  }
1009  return m_id3v2Tags.front().get();
1010 }
1011 
1025 {
1026  if (!tag) {
1027  return;
1028  }
1029  if (m_container) {
1030  m_container->removeTag(tag);
1031  }
1032  if (m_id3v1Tag.get() == tag) {
1033  m_id3v1Tag.reset();
1034  }
1035  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1036  if (i->get() == tag) {
1037  m_id3v2Tags.erase(i);
1038  break;
1039  }
1040  }
1041 }
1042 
1049 {
1050  if (m_container) {
1051  m_container->removeAllTags();
1052  }
1053  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1054  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1055  }
1056  m_id3v1Tag.reset();
1057  m_id3v2Tags.clear();
1058 }
1059 
1064 {
1065  if (m_container && m_container->chapterCount()) {
1066  return true;
1067  }
1068  switch (m_containerFormat) {
1070  case ContainerFormat::Webm:
1071  return true;
1072  default:
1073  return false;
1074  }
1075 }
1076 
1081 {
1082  if (m_container && m_container->attachmentCount()) {
1083  return true;
1084  }
1085  switch (m_containerFormat) {
1087  case ContainerFormat::Webm:
1088  return true;
1089  default:
1090  return false;
1091  }
1092 }
1093 
1098 {
1099  if (trackCount()) {
1100  return true;
1101  }
1102  switch (m_containerFormat) {
1103  case ContainerFormat::Mp4:
1106  case ContainerFormat::Ogg:
1108  case ContainerFormat::Webm:
1109  return true;
1110  default:
1111  return false;
1112  }
1113 }
1114 
1119 {
1120  switch (m_containerFormat) {
1121  case ContainerFormat::Adts:
1122  case ContainerFormat::Flac:
1125  case ContainerFormat::Mp4:
1126  case ContainerFormat::Ogg:
1128  case ContainerFormat::Webm:
1129  // these container formats are supported
1130  return true;
1131  default:
1132  // the container format is unsupported
1133  // -> an ID3 tag might be already present, in this case the tags are considered supported
1134  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1135  }
1136 }
1137 
1144 {
1145  // simply return the first tag here since MP4 files never contain multiple tags
1146  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1147  && m_container->tagCount() > 0
1148  ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1149  : nullptr;
1150 }
1151 
1158 const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1159 {
1160  // matroska files might contain multiple tags (targeting different scopes)
1161  if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1162  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1163  } else {
1164  static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1165  return empty;
1166  }
1167 }
1168 
1175 {
1176  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1177  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1178  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1179 }
1180 
1186 vector<AbstractChapter *> MediaFileInfo::chapters() const
1187 {
1188  vector<AbstractChapter *> res;
1189  if (m_container) {
1190  const size_t count = m_container->chapterCount();
1191  res.reserve(count);
1192  for (size_t i = 0; i != count; ++i) {
1193  res.push_back(m_container->chapter(i));
1194  }
1195  }
1196  return res;
1197 }
1198 
1204 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1205 {
1206  vector<AbstractAttachment *> res;
1207  if (m_container) {
1208  const size_t count = m_container->attachmentCount();
1209  res.reserve(count);
1210  for (size_t i = 0; i != count; ++i) {
1211  res.push_back(m_container->attachment(i));
1212  }
1213  }
1214  return res;
1215 }
1216 
1229 {
1230  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1231  m_containerFormat = ContainerFormat::Unknown;
1232  m_containerOffset = 0;
1233  m_paddingSize = 0;
1234  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1235  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1236  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1237  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1238  m_id3v1Tag.reset();
1239  m_id3v2Tags.clear();
1240  m_actualId3v2TagOffsets.clear();
1241  m_actualExistingId3v1Tag = false;
1242  m_container.reset();
1243  m_singleTrack.reset();
1244 }
1245 
1263 {
1264  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1265  if (begin == end) {
1266  return;
1267  }
1268  Id3v2Tag &first = **begin;
1269  auto isecond = begin + 1;
1270  if (isecond == end) {
1271  return;
1272  }
1273  for (auto i = isecond; i != end; ++i) {
1274  first.insertFields(**i, false);
1275  }
1276  m_id3v2Tags.erase(isecond, end - 1);
1277 }
1278 
1290 {
1292  return false;
1293  }
1296 }
1297 
1309 {
1311  return false;
1312  }
1315 }
1316 
1332 {
1333  switch (m_containerFormat) {
1334  case ContainerFormat::Ogg:
1335  if (m_container) {
1336  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1337  }
1338  break;
1339  case ContainerFormat::Flac:
1340  if (m_singleTrack) {
1341  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1342  }
1343  break;
1344  default:;
1345  }
1346  return nullptr;
1347 }
1348 
1359 {
1360  switch (m_containerFormat) {
1361  case ContainerFormat::Ogg:
1362  if (m_container) {
1363  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1364  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1365  return hadTags;
1366  }
1367  break;
1368  case ContainerFormat::Flac:
1369  if (m_singleTrack) {
1370  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1371  }
1372  break;
1373  default:;
1374  }
1375  return false;
1376 }
1377 
1386 void MediaFileInfo::tags(vector<Tag *> &tags) const
1387 {
1388  if (hasId3v1Tag()) {
1389  tags.push_back(m_id3v1Tag.get());
1390  }
1391  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1392  tags.push_back(tag.get());
1393  }
1394  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1395  if (auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1396  tags.push_back(vorbisComment);
1397  }
1398  }
1399  if (m_container) {
1400  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1401  tags.push_back(m_container->tag(i));
1402  }
1403  }
1404 }
1405 
1410 {
1411  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1412  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1413 }
1414 
1421 vector<Tag *> MediaFileInfo::tags() const
1422 {
1423  vector<Tag *> res;
1424  tags(res);
1425  return res;
1426 }
1427 
1432 {
1435 }
1436 
1440 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1441 {
1442  static const string context("making MP3/FLAC file");
1443  // there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
1444  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1445  && m_containerFormat != ContainerFormat::Flac) {
1446  if (m_actualExistingId3v1Tag) {
1447  // there is currently an ID3v1 tag at the end of the file
1448  if (m_id3v1Tag) {
1449  // the file shall still have an ID3v1 tag
1450  progress.updateStep("Updating ID3v1 tag ...");
1451  // ensure the file is still open / not readonly
1452  open();
1453  stream().seekp(-128, ios_base::end);
1454  try {
1455  m_id3v1Tag->make(stream(), diag);
1456  } catch (const Failure &) {
1457  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1458  }
1459  } else {
1460  // the currently existing ID3v1 tag shall be removed
1461  progress.updateStep("Removing ID3v1 tag ...");
1462  stream().close();
1463  if (truncate(path().c_str(), size() - 128) == 0) {
1464  reportSizeChanged(size() - 128);
1465  } else {
1466  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1467  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1468  }
1469  }
1470 
1471  } else {
1472  // there is currently no ID3v1 tag at the end of the file
1473  if (m_id3v1Tag) {
1474  progress.updateStep("Adding ID3v1 tag ...");
1475  // ensure the file is still open / not readonly
1476  open();
1477  stream().seekp(0, ios_base::end);
1478  try {
1479  m_id3v1Tag->make(stream(), diag);
1480  } catch (const Failure &) {
1481  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1482  }
1483  } else {
1484  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1485  }
1486  }
1487 
1488  } else {
1489  // ID3v2 needs to be modified
1490  progress.updateStep("Updating ID3v2 tags ...");
1491 
1492  // prepare ID3v2 tags
1493  vector<Id3v2TagMaker> makers;
1494  makers.reserve(m_id3v2Tags.size());
1495  uint32 tagsSize = 0;
1496  for (auto &tag : m_id3v2Tags) {
1497  try {
1498  makers.emplace_back(tag->prepareMaking(diag));
1499  tagsSize += makers.back().requiredSize();
1500  } catch (const Failure &) {
1501  }
1502  }
1503 
1504  // check whether it is a raw FLAC stream
1505  FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1506  uint32 streamOffset; // where the actual stream starts
1507  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1508  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1509  uint32 startOfLastMetaDataBlock;
1510 
1511  if (flacStream) {
1512  // if it is a raw FLAC stream, make FLAC metadata
1513  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1514  tagsSize += flacMetaData.tellp();
1515  streamOffset = flacStream->streamOffset();
1516  } else {
1517  // make no further metadata, just use the container offset as stream offset
1518  streamOffset = static_cast<uint32>(m_containerOffset);
1519  }
1520 
1521  // check whether rewrite is required
1522  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1523  uint32 padding = 0;
1524  if (!rewriteRequired) {
1525  // rewriting is not forced and new tag is not too big for available space
1526  // -> calculate new padding
1527  padding = streamOffset - tagsSize;
1528  // -> check whether the new padding matches specifications
1529  if (padding < minPadding() || padding > maxPadding()) {
1530  rewriteRequired = true;
1531  }
1532  }
1533  if (makers.empty() && !flacStream) {
1534  // an ID3v2 tag is not written and it is not a FLAC stream
1535  // -> can't include padding
1536  if (padding) {
1537  // but padding would be present -> need to rewrite
1538  padding = 0; // can't write the preferred padding despite rewriting
1539  rewriteRequired = true;
1540  }
1541  } else if (rewriteRequired) {
1542  // rewriting is forced or new ID3v2 tag is too big for available space
1543  // -> use preferred padding when rewriting anyways
1544  padding = preferredPadding();
1545  } else if (makers.empty() && flacStream && padding && padding < 4) {
1546  // no ID3v2 tag -> must include padding in FLAC stream
1547  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1548  padding = preferredPadding();
1549  rewriteRequired = true;
1550  }
1551  if (rewriteRequired && flacStream && makers.empty() && padding) {
1552  // the first 4 byte of FLAC padding actually don't count because these
1553  // can not be used for additional meta data
1554  padding += 4;
1555  }
1556  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1557 
1558  // setup stream(s) for writing
1559  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1560  string backupPath;
1561  NativeFileStream &outputStream = stream();
1562  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1563 
1564  if (rewriteRequired) {
1565  if (m_saveFilePath.empty()) {
1566  // move current file to temp dir and reopen it as backupStream, recreate original file
1567  try {
1568  BackupHelper::createBackupFile(path(), backupPath, outputStream, backupStream);
1569  // recreate original file, define buffer variables
1570  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1571  } catch (...) {
1572  const char *what = catchIoFailure();
1573  diag.emplace_back(DiagLevel::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1574  throwIoFailure(what);
1575  }
1576  } else {
1577  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1578  try {
1579  close();
1580  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1581  backupStream.open(path(), ios_base::in | ios_base::binary);
1582  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1583  } catch (...) {
1584  const char *what = catchIoFailure();
1585  diag.emplace_back(DiagLevel::Critical, "Opening streams to write output file failed.", context);
1586  throwIoFailure(what);
1587  }
1588  }
1589 
1590  } else { // !rewriteRequired
1591  // reopen original file to ensure it is opened for writing
1592  try {
1593  close();
1594  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1595  } catch (...) {
1596  const char *what = catchIoFailure();
1597  diag.emplace_back(DiagLevel::Critical, "Opening the file with write permissions failed.", context);
1598  throwIoFailure(what);
1599  }
1600  }
1601 
1602  // start actual writing
1603  try {
1604  if (!makers.empty()) {
1605  // write ID3v2 tags
1606  progress.updateStep("Writing ID3v2 tag ...");
1607  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1608  i->make(outputStream, 0, diag);
1609  }
1610  // include padding into the last ID3v2 tag
1611  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding, diag);
1612  }
1613 
1614  if (flacStream) {
1615  if (padding && startOfLastMetaDataBlock) {
1616  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1617  flacMetaData.seekg(startOfLastMetaDataBlock);
1618  flacMetaData.seekp(startOfLastMetaDataBlock);
1619  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1620  flacMetaData.seekg(0);
1621  }
1622 
1623  // write FLAC metadata
1624  outputStream << flacMetaData.rdbuf();
1625 
1626  // write padding
1627  if (padding) {
1628  flacStream->makePadding(outputStream, padding, true, diag);
1629  }
1630  }
1631 
1632  if (makers.empty() && !flacStream) {
1633  // just write padding (however, padding should be set to 0 in this case?)
1634  for (; padding; --padding) {
1635  outputStream.put(0);
1636  }
1637  }
1638 
1639  // copy / skip actual stream data
1640  // -> determine media data size
1641  uint64 mediaDataSize = size() - streamOffset;
1642  if (m_actualExistingId3v1Tag) {
1643  mediaDataSize -= 128;
1644  }
1645 
1646  if (rewriteRequired) {
1647  // copy data from original file
1648  switch (m_containerFormat) {
1650  progress.updateStep("Writing MPEG audio frames ...");
1651  break;
1652  default:
1653  progress.updateStep("Writing frames ...");
1654  }
1655  backupStream.seekg(streamOffset);
1656  CopyHelper<0x4000> copyHelper;
1657  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1658  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1659  } else {
1660  // just skip actual stream data
1661  outputStream.seekp(mediaDataSize, ios_base::cur);
1662  }
1663 
1664  // write ID3v1 tag
1665  if (m_id3v1Tag) {
1666  progress.updateStep("Writing ID3v1 tag ...");
1667  try {
1668  m_id3v1Tag->make(stream(), diag);
1669  } catch (const Failure &) {
1670  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1671  }
1672  }
1673 
1674  // handle streams
1675  if (rewriteRequired) {
1676  // report new size
1677  reportSizeChanged(outputStream.tellp());
1678  // "save as path" is now the regular path
1679  if (!saveFilePath().empty()) {
1681  m_saveFilePath.clear();
1682  }
1683  // stream is useless for further usage because it is write-only
1684  outputStream.close();
1685  } else {
1686  const auto newSize = static_cast<uint64>(outputStream.tellp());
1687  if (newSize < size()) {
1688  // file is smaller after the modification -> truncate
1689  // -> close stream before truncating
1690  outputStream.close();
1691  // -> truncate file
1692  if (truncate(path().c_str(), newSize) == 0) {
1693  reportSizeChanged(newSize);
1694  } else {
1695  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1696  }
1697  } else {
1698  // file is longer after the modification -> just report new size
1699  reportSizeChanged(newSize);
1700  }
1701  }
1702 
1703  } catch (...) {
1704  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1705  }
1706  }
1707 }
1708 }
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.
void open(bool readOnly=false)
Opens a std::fstream for the current file.
TAG_PARSER_EXPORT const char * description()
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:239
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string...
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.
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
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.
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.
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...
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.
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.
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.
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)
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().
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.
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
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.
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.
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
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
ChronoUtilities::TimeSpan duration() const
Returns the overall duration of the file is known; otherwise returns a TimeSpan with zero ticks...