Tag Parser  8.0.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1 #include "./mediafileinfo.h"
2 #include "./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 "./ivf/ivfstream.h"
20 
21 #include "./mp4/mp4atom.h"
22 #include "./mp4/mp4container.h"
23 #include "./mp4/mp4ids.h"
24 #include "./mp4/mp4tag.h"
25 #include "./mp4/mp4track.h"
26 
27 #include "./matroska/ebmlelement.h"
29 #include "./matroska/matroskatag.h"
31 
32 #include "./ogg/oggcontainer.h"
33 
34 #include "./flac/flacmetadata.h"
35 #include "./flac/flacstream.h"
36 
37 #include <c++utilities/chrono/timespan.h>
38 #include <c++utilities/conversion/stringconversion.h>
39 #include <c++utilities/io/catchiofailure.h>
40 
41 #include <unistd.h>
42 
43 #include <algorithm>
44 #include <cstdio>
45 #include <functional>
46 #include <iomanip>
47 #include <ios>
48 #include <memory>
49 #include <system_error>
50 
51 using namespace std;
52 using namespace std::placeholders;
53 using namespace IoUtilities;
54 using namespace ConversionUtilities;
55 using namespace ChronoUtilities;
56 
62 namespace TagParser {
63 
64 #ifdef FORCE_FULL_PARSE_DEFAULT
65 #define MEDIAINFO_CPP_FORCE_FULL_PARSE true
66 #else
67 #define MEDIAINFO_CPP_FORCE_FULL_PARSE false
68 #endif
69 
83 MediaFileInfo::MediaFileInfo()
84  : m_containerParsingStatus(ParsingStatus::NotParsedYet)
85  , m_containerFormat(ContainerFormat::Unknown)
86  , m_containerOffset(0)
87  , m_actualExistingId3v1Tag(false)
88  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
89  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
90  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
91  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
92  , m_minPadding(0)
93  , m_maxPadding(0)
94  , m_preferredPadding(0)
95  , m_tagPosition(ElementPosition::BeforeData)
96  , m_indexPosition(ElementPosition::BeforeData)
97  , m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
98  , m_forceRewrite(true)
99  , m_forceTagPosition(true)
100  , m_forceIndexPosition(true)
101 {
102 }
103 
109 MediaFileInfo::MediaFileInfo(const string &path)
110  : BasicFileInfo(path)
111  , m_containerParsingStatus(ParsingStatus::NotParsedYet)
112  , m_containerFormat(ContainerFormat::Unknown)
113  , m_containerOffset(0)
114  , m_actualExistingId3v1Tag(false)
115  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
116  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
117  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
118  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
119  , m_minPadding(0)
120  , m_maxPadding(0)
121  , m_preferredPadding(0)
122  , m_tagPosition(ElementPosition::BeforeData)
123  , m_indexPosition(ElementPosition::BeforeData)
124  , m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
125  , m_forceRewrite(true)
126  , m_forceTagPosition(true)
127  , m_forceIndexPosition(true)
128 {
129 }
130 
135 {
136 }
137 
154 {
155  // skip if container format already parsed
157  return;
158  }
159 
160  static const string context("parsing file header");
161  open(); // ensure the file is open
162  m_containerFormat = ContainerFormat::Unknown;
163 
164  // file size
165  m_paddingSize = 0;
166  m_containerOffset = 0;
167 
168  // read signatrue
169  char buff[16];
170  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
171 startParsingSignature:
172  if (size() - containerOffset() >= 16) {
173  stream().seekg(m_containerOffset, ios_base::beg);
174  stream().read(buff, sizeof(buff));
175 
176  // skip zero bytes/padding
177  size_t bytesSkipped = 0;
178  for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
179  ;
180  if (bytesSkipped >= 4) {
181  m_containerOffset += bytesSkipped;
182 
183  // give up after 0x100 bytes
184  if ((m_paddingSize += bytesSkipped) >= 0x100u) {
185  m_containerParsingStatus = ParsingStatus::NotSupported;
186  m_containerFormat = ContainerFormat::Unknown;
187  return;
188  }
189 
190  // try again
191  goto startParsingSignature;
192  }
193  if (m_paddingSize) {
194  diag.emplace_back(DiagLevel::Warning, argsToString(m_paddingSize, " zero-bytes skipped at the beginning of the file."), context);
195  }
196 
197  // parse signature
198  switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
200  // save position of ID3v2 tag
201  m_actualId3v2TagOffsets.push_back(m_containerOffset);
202  if (m_actualId3v2TagOffsets.size() == 2) {
203  diag.emplace_back(DiagLevel::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
204  }
205 
206  // read ID3v2 header
207  stream().seekg(m_containerOffset + 5, ios_base::beg);
208  stream().read(buff, 5);
209 
210  // set the container offset to skip ID3v2 header
211  m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
212  if ((*buff) & 0x10) {
213  // footer present
214  m_containerOffset += 10;
215  }
216 
217  // continue reading signature
218  goto startParsingSignature;
219 
222  // MP4/QuickTime is handled using Mp4Container instance
223  m_container = make_unique<Mp4Container>(*this, m_containerOffset);
224  try {
225  static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
226  } catch (const Failure &) {
227  m_containerParsingStatus = ParsingStatus::CriticalFailure;
228  }
229  break;
230  }
231  case ContainerFormat::Ebml: {
232  // EBML/Matroska is handled using MatroskaContainer instance
233  auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
234  try {
235  container->parseHeader(diag);
236  if (container->documentType() == "matroska") {
237  m_containerFormat = ContainerFormat::Matroska;
238  } else if (container->documentType() == "webm") {
239  m_containerFormat = ContainerFormat::Webm;
240  }
241  if (m_forceFullParse) {
242  // validating the element structure of Matroska files takes too long when
243  // parsing big files so do this only when explicitely desired
244  container->validateElementStructure(diag, &m_paddingSize);
245  container->validateIndex(diag);
246  }
247  } catch (const Failure &) {
248  m_containerParsingStatus = ParsingStatus::CriticalFailure;
249  }
250  m_container = move(container);
251  break;
252  }
254  // Ogg is handled by OggContainer instance
255  m_container = make_unique<OggContainer>(*this, m_containerOffset);
256  static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
257  break;
259  // container format is still unknown -> check for magic numbers at odd offsets
260  // -> check for tar (magic number at offset 0x101)
261  if (size() > 0x107) {
262  stream().seekg(0x101);
263  stream().read(buff, 6);
264  if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
265  m_containerFormat = ContainerFormat::Tar;
266  break;
267  }
268  }
269  break;
270  default:;
271  }
272  }
273 
274  // set parsing status
275  if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
276  if (m_containerFormat == ContainerFormat::Unknown) {
277  m_containerParsingStatus = ParsingStatus::NotSupported;
278  } else {
279  m_containerParsingStatus = ParsingStatus::Ok;
280  }
281  }
282 }
283 
298 {
299  // skip if tracks already parsed
301  return;
302  }
303  static const string context("parsing tracks");
304 
305  try {
306  // parse tracks via container object
307  if (m_container) {
308  m_container->parseTracks(diag);
309  m_tracksParsingStatus = ParsingStatus::Ok;
310  return;
311  }
312 
313  // parse tracks via track object for "single-track"-formats
314  switch (m_containerFormat) {
316  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
317  break;
319  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
320  break;
322  m_singleTrack = make_unique<IvfStream>(stream(), m_containerOffset);
323  break;
325  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
326  break;
328  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
329  break;
330  default:
331  throw NotImplementedException();
332  }
333  m_singleTrack->parseHeader(diag);
334 
335  // take padding for some "single-track" formats into account
336  switch (m_containerFormat) {
338  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
339  break;
340  default:;
341  }
342 
343  m_tracksParsingStatus = ParsingStatus::Ok;
344 
345  } catch (const NotImplementedException &) {
346  diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
347  m_tracksParsingStatus = ParsingStatus::NotSupported;
348  } catch (const Failure &) {
349  diag.emplace_back(DiagLevel::Critical, "Unable to parse tracks.", context);
350  m_tracksParsingStatus = ParsingStatus::CriticalFailure;
351  }
352 }
353 
369 {
370  // skip if tags already parsed
372  return;
373  }
374  static const string context("parsing tag");
375 
376  // check for ID3v1 tag
377  if (size() >= 128) {
378  m_id3v1Tag = make_unique<Id3v1Tag>();
379  try {
380  stream().seekg(-128, ios_base::end);
381  m_id3v1Tag->parse(stream(), diag);
382  m_actualExistingId3v1Tag = true;
383  } catch (const NoDataFoundException &) {
384  m_id3v1Tag.reset();
385  } catch (const Failure &) {
386  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
387  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
388  }
389  }
390 
391  // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
392  m_id3v2Tags.clear();
393  for (const auto offset : m_actualId3v2TagOffsets) {
394  auto id3v2Tag = make_unique<Id3v2Tag>();
395  stream().seekg(offset, ios_base::beg);
396  try {
397  id3v2Tag->parse(stream(), size() - static_cast<uint64>(offset), diag);
398  m_paddingSize += id3v2Tag->paddingSize();
399  } catch (const NoDataFoundException &) {
400  continue;
401  } catch (const Failure &) {
402  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
403  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v2 tag.", context);
404  }
405  m_id3v2Tags.emplace_back(id3v2Tag.release());
406  }
407 
408  // check for tags in tracks (FLAC only) or via container object
409  try {
410  if (m_containerFormat == ContainerFormat::Flac) {
411  parseTracks(diag);
412  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
413  m_tagsParsingStatus = m_tracksParsingStatus;
414  }
415  return;
416  } else if (m_container) {
417  m_container->parseTags(diag);
418  } else {
419  throw NotImplementedException();
420  }
421 
422  // set status, but do not override error/unsupported status form ID3 tags here
423  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
424  m_tagsParsingStatus = ParsingStatus::Ok;
425  }
426 
427  } catch (const NotImplementedException &) {
428  // set status to not supported, but do not override parsing status from ID3 tags here
429  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
430  m_tagsParsingStatus = ParsingStatus::NotSupported;
431  }
432  diag.emplace_back(DiagLevel::Information, "Parsing tags is not implemented for the container format of the file.", context);
433  } catch (const Failure &) {
434  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
435  diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
436  }
437 }
438 
451 {
452  // skip if chapters already parsed
454  return;
455  }
456  static const string context("parsing chapters");
457 
458  try {
459  // parse chapters via container object
460  if (!m_container) {
461  throw NotImplementedException();
462  }
463  m_container->parseChapters(diag);
464  m_chaptersParsingStatus = ParsingStatus::Ok;
465  } catch (const NotImplementedException &) {
466  m_chaptersParsingStatus = ParsingStatus::NotSupported;
467  diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
468  } catch (const Failure &) {
469  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
470  diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
471  }
472 }
473 
486 {
487  // skip if attachments already parsed
489  return;
490  }
491  static const string context("parsing attachments");
492 
493  try {
494  // parse attachments via container object
495  if (!m_container) {
496  throw NotImplementedException();
497  }
498  m_container->parseAttachments(diag);
499  m_attachmentsParsingStatus = ParsingStatus::Ok;
500  } catch (const NotImplementedException &) {
501  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
502  diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
503  } catch (const Failure &) {
504  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
505  diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
506  }
507 }
508 
516 {
517  parseContainerFormat(diag);
518  parseTracks(diag);
519  parseTags(diag);
520  parseChapters(diag);
521  parseAttachments(diag);
522 }
523 
536 {
537  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
539  return false;
540  }
541 
542  // check if tags need to be created/adjusted/removed
543  const auto requiredTargets(settings.requiredTargets);
544  const auto flags(settings.flags);
545  const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
546  auto targetsSupported = false;
547  if (areTagsSupported() && m_container) {
548  // container object takes care of tag management
549  if (targetsRequired) {
550  // check whether container supports targets
551  if (m_container->tagCount()) {
552  // all tags in the container should support targets if the first one supports targets
553  targetsSupported = m_container->tag(0)->supportsTarget();
554  } else {
555  // try to create a new tag and check whether targets are supported
556  auto *const tag = m_container->createTag();
557  if (tag && (targetsSupported = tag->supportsTarget())) {
558  tag->setTarget(requiredTargets.front());
559  }
560  }
561  if (targetsSupported) {
562  for (const auto &target : requiredTargets) {
563  m_container->createTag(target);
564  }
565  }
566  } else {
567  // no targets are required -> just ensure that at least one tag is present
568  m_container->createTag();
569  }
570  return true;
571  }
572 
573  // no container object present
574  switch (m_containerFormat) {
576  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
577  break;
578  default:
579  // create ID3 tag(s)
581  switch (containerFormat()) {
585  break;
586  default:
587  return false;
588  }
589  }
590  // create ID3 tags according to id3v2usage and id3v2usage
591  // always create ID3v1 tag -> ensure there is one
592  if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
593  auto *const id3v1Tag = createId3v1Tag();
594  if (flags & TagCreationFlags::Id3InitOnCreate) {
595  for (const auto &id3v2Tag : id3v2Tags()) {
596  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
597  id3v1Tag->insertValues(*id3v2Tag, true);
598  // ID3v1 does not support all text encodings which might be used in ID3v2
600  }
601  }
602  }
603  if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
604  // always create ID3v2 tag -> ensure there is one and set version
605  auto *const id3v2Tag = createId3v2Tag();
606  id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
607  if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
608  id3v2Tag->insertValues(*id3v1Tag(), true);
609  }
610  }
611  }
612 
614  mergeId3v2Tags();
615  }
616  // remove ID3 tags according to settings
617  if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
618  // transfer tags to ID3v2 tag before removing
620  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
621  }
622  removeId3v1Tag();
623  }
624  if (settings.id3v2usage == TagUsage::Never) {
626  // transfer tags to ID3v1 tag before removing
627  for (const auto &tag : id3v2Tags()) {
628  id3v1Tag()->insertValues(*tag, false);
629  }
630  }
632  } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
633  // set version of ID3v2 tag according user preferences
634  for (const auto &tag : id3v2Tags()) {
635  tag->setVersion(settings.id3v2MajorVersion, 0);
636  }
637  }
638  return true;
639 }
640 
662 {
663  static const string context("making file");
664  diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
665  bool previousParsingSuccessful = true;
666  switch (tagsParsingStatus()) {
667  case ParsingStatus::Ok:
669  break;
670  default:
671  previousParsingSuccessful = false;
672  diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
673  }
674  switch (tracksParsingStatus()) {
675  case ParsingStatus::Ok:
677  break;
678  default:
679  previousParsingSuccessful = false;
680  diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
681  }
682  if (!previousParsingSuccessful) {
683  throw InvalidDataException();
684  }
685  if (m_container) { // container object takes care
686  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
687  if (hasId3v1Tag()) {
688  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
689  }
690  if (hasId3v2Tag()) {
691  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
692  }
693  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
694  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
695  try {
696  m_container->makeFile(diag, progress);
697  } catch (...) {
698  // since the file might be messed up, invalidate the parsing results
700  throw;
701  }
702  } else { // implementation if no container object is present
703  // assume the file is a MP3 file
704  try {
705  makeMp3File(diag, progress);
706  } catch (...) {
707  // since the file might be messed up, invalidate the parsing results
709  throw;
710  }
711  }
713 }
714 
728 {
729  MediaType mediaType = MediaType::Unknown;
730  unsigned int version = 0;
731  switch (m_containerFormat) {
732  case ContainerFormat::Ogg: {
733  // check for video track or whether only Opus or Speex tracks are present
734  const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
735  if (tracks.empty()) {
736  break;
737  }
738  bool onlyOpus = true, onlySpeex = true;
739  for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
740  if (track->mediaType() == MediaType::Video) {
741  mediaType = MediaType::Video;
742  }
743  if (track->format().general != GeneralMediaFormat::Opus) {
744  onlyOpus = false;
745  }
746  if (track->format().general != GeneralMediaFormat::Speex) {
747  onlySpeex = false;
748  }
749  }
750  if (onlyOpus) {
751  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
752  } else if (onlySpeex) {
753  version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
754  }
755  break;
756  }
760  break;
762  if (m_singleTrack) {
763  version = m_singleTrack->format().sub;
764  }
765  break;
766  default:;
767  }
768  return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
769 }
770 
781 const char *MediaFileInfo::mimeType() const
782 {
783  MediaType mediaType;
784  switch (m_containerFormat) {
789  break;
790  default:
791  mediaType = MediaType::Unknown;
792  }
793  return TagParser::containerMimeType(m_containerFormat, mediaType);
794 }
795 
808 vector<AbstractTrack *> MediaFileInfo::tracks() const
809 {
810  vector<AbstractTrack *> res;
811  size_t trackCount = 0;
812  size_t containerTrackCount = 0;
813  if (m_singleTrack) {
814  trackCount = 1;
815  }
816  if (m_container) {
817  trackCount += (containerTrackCount = m_container->trackCount());
818  }
819  res.reserve(trackCount);
820 
821  if (m_singleTrack) {
822  res.push_back(m_singleTrack.get());
823  }
824  for (size_t i = 0; i != containerTrackCount; ++i) {
825  res.push_back(m_container->track(i));
826  }
827  return res;
828 }
829 
839 {
841  return false;
842  }
843  if (m_singleTrack && m_singleTrack->mediaType() == type) {
844  return true;
845  } else if (m_container) {
846  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
847  if (m_container->track(i)->mediaType() == type) {
848  return true;
849  }
850  }
851  }
852  return false;
853 }
854 
865 {
866  if (m_container) {
867  return m_container->duration();
868  } else if (m_singleTrack) {
869  return m_singleTrack->duration();
870  }
871  return TimeSpan();
872 }
873 
884 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
885 {
886  unordered_set<string> res;
887  if (m_container) {
888  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
889  const AbstractTrack *track = m_container->track(i);
890  if ((type == MediaType::Unknown || track->mediaType() == type) && !track->language().empty() && track->language() != "und") {
891  res.emplace(track->language());
892  }
893  }
894  } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
895  && m_singleTrack->language() != "und") {
896  res.emplace(m_singleTrack->language());
897  }
898  return res;
899 }
900 
913 {
914  if (m_container) {
915  const size_t trackCount = m_container->trackCount();
916  vector<string> parts;
917  parts.reserve(trackCount);
918  for (size_t i = 0; i != trackCount; ++i) {
919  const string description(m_container->track(i)->description());
920  if (!description.empty()) {
921  parts.emplace_back(move(description));
922  }
923  }
924  return joinStrings(parts, " / ");
925  } else if (m_singleTrack) {
926  return m_singleTrack->description();
927  }
928  return string();
929 }
930 
941 {
943  return false;
944  }
945  if (m_id3v1Tag) {
946  m_id3v1Tag.reset();
947  return true;
948  }
949  return false;
950 }
951 
968 {
970  return nullptr;
971  }
972  if (!m_id3v1Tag) {
973  m_id3v1Tag = make_unique<Id3v1Tag>();
974  }
975  return m_id3v1Tag.get();
976 }
977 
989 {
991  return false;
992  }
993  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
994  if (i->get() == tag) {
995  m_id3v2Tags.erase(i);
996  return true;
997  }
998  }
999  return false;
1000 }
1001 
1011 {
1012  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1013  return false;
1014  }
1015  m_id3v2Tags.clear();
1016  return true;
1017 }
1018 
1035 {
1036  if (m_id3v2Tags.empty()) {
1037  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1038  }
1039  return m_id3v2Tags.front().get();
1040 }
1041 
1056 {
1057  if (!tag) {
1058  return false;
1059  }
1060 
1061  // remove tag via container
1062  if (m_container) {
1063  return m_container->removeTag(tag);
1064  }
1065 
1066  // remove tag via track for "single-track" formats
1067  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1068  auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1069  if (flacStream->vorbisComment() == tag) {
1070  return flacStream->removeVorbisComment();
1071  }
1072  }
1073 
1074  // remove ID3 tags
1075  if (m_id3v1Tag.get() == tag) {
1076  m_id3v1Tag.reset();
1077  return true;
1078  }
1079  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1080  if (i->get() == tag) {
1081  m_id3v2Tags.erase(i);
1082  return true;
1083  }
1084  }
1085  return false;
1086 }
1087 
1095 {
1096  if (m_container) {
1097  m_container->removeAllTags();
1098  }
1099  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1100  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1101  }
1102  m_id3v1Tag.reset();
1103  m_id3v2Tags.clear();
1104 }
1105 
1110 {
1111  if (m_container && m_container->chapterCount()) {
1112  return true;
1113  }
1114  switch (m_containerFormat) {
1116  case ContainerFormat::Webm:
1117  return true;
1118  default:
1119  return false;
1120  }
1121 }
1122 
1127 {
1128  if (m_container && m_container->attachmentCount()) {
1129  return true;
1130  }
1131  switch (m_containerFormat) {
1133  case ContainerFormat::Webm:
1134  return true;
1135  default:
1136  return false;
1137  }
1138 }
1139 
1144 {
1145  if (trackCount()) {
1146  return true;
1147  }
1148  switch (m_containerFormat) {
1149  case ContainerFormat::Mp4:
1152  case ContainerFormat::Ogg:
1154  case ContainerFormat::Webm:
1155  return true;
1156  default:
1157  return false;
1158  }
1159 }
1160 
1165 {
1166  switch (m_containerFormat) {
1167  case ContainerFormat::Adts:
1168  case ContainerFormat::Flac:
1171  case ContainerFormat::Mp4:
1172  case ContainerFormat::Ogg:
1174  case ContainerFormat::Webm:
1175  // these container formats are supported
1176  return true;
1177  default:
1178  // the container format is unsupported
1179  // -> an ID3 tag might be already present, in this case the tags are considered supported
1180  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1181  }
1182 }
1183 
1190 {
1191  // simply return the first tag here since MP4 files never contain multiple tags
1192  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1193  && m_container->tagCount() > 0
1194  ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1195  : nullptr;
1196 }
1197 
1204 const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1205 {
1206  // matroska files might contain multiple tags (targeting different scopes)
1207  if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1208  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1209  } else {
1210  static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1211  return empty;
1212  }
1213 }
1214 
1221 {
1222  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1223  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1224  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1225 }
1226 
1232 vector<AbstractChapter *> MediaFileInfo::chapters() const
1233 {
1234  vector<AbstractChapter *> res;
1235  if (m_container) {
1236  const size_t count = m_container->chapterCount();
1237  res.reserve(count);
1238  for (size_t i = 0; i != count; ++i) {
1239  res.push_back(m_container->chapter(i));
1240  }
1241  }
1242  return res;
1243 }
1244 
1250 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1251 {
1252  vector<AbstractAttachment *> res;
1253  if (m_container) {
1254  const size_t count = m_container->attachmentCount();
1255  res.reserve(count);
1256  for (size_t i = 0; i != count; ++i) {
1257  res.push_back(m_container->attachment(i));
1258  }
1259  }
1260  return res;
1261 }
1262 
1275 {
1276  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1277  m_containerFormat = ContainerFormat::Unknown;
1278  m_containerOffset = 0;
1279  m_paddingSize = 0;
1280  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1281  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1282  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1283  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1284  m_id3v1Tag.reset();
1285  m_id3v2Tags.clear();
1286  m_actualId3v2TagOffsets.clear();
1287  m_actualExistingId3v1Tag = false;
1288  m_container.reset();
1289  m_singleTrack.reset();
1290 }
1291 
1309 {
1310  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1311  if (begin == end) {
1312  return;
1313  }
1314  Id3v2Tag &first = **begin;
1315  auto isecond = begin + 1;
1316  if (isecond == end) {
1317  return;
1318  }
1319  for (auto i = isecond; i != end; ++i) {
1320  first.insertFields(**i, false);
1321  }
1322  m_id3v2Tags.erase(isecond, end - 1);
1323 }
1324 
1336 {
1338  return false;
1339  }
1342 }
1343 
1355 {
1357  return false;
1358  }
1361 }
1362 
1378 {
1379  switch (m_containerFormat) {
1380  case ContainerFormat::Ogg:
1381  if (m_container) {
1382  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1383  }
1384  break;
1385  case ContainerFormat::Flac:
1386  if (m_singleTrack) {
1387  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1388  }
1389  break;
1390  default:;
1391  }
1392  return nullptr;
1393 }
1394 
1405 {
1406  switch (m_containerFormat) {
1407  case ContainerFormat::Ogg:
1408  if (m_container) {
1409  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1410  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1411  return hadTags;
1412  }
1413  break;
1414  case ContainerFormat::Flac:
1415  if (m_singleTrack) {
1416  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1417  }
1418  break;
1419  default:;
1420  }
1421  return false;
1422 }
1423 
1432 void MediaFileInfo::tags(vector<Tag *> &tags) const
1433 {
1434  if (hasId3v1Tag()) {
1435  tags.push_back(m_id3v1Tag.get());
1436  }
1437  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1438  tags.push_back(tag.get());
1439  }
1440  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1441  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1442  tags.push_back(vorbisComment);
1443  }
1444  }
1445  if (m_container) {
1446  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1447  tags.push_back(m_container->tag(i));
1448  }
1449  }
1450 }
1451 
1456 {
1457  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1458  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1459 }
1460 
1467 vector<Tag *> MediaFileInfo::tags() const
1468 {
1469  vector<Tag *> res;
1470  tags(res);
1471  return res;
1472 }
1473 
1478 {
1481 }
1482 
1486 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1487 {
1488  static const string context("making MP3/FLAC file");
1489 
1490  // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1491  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1492  && m_containerFormat != ContainerFormat::Flac) {
1493  // alter ID3v1 tag
1494  if (!m_id3v1Tag) {
1495  // remove ID3v1 tag
1496  if (!m_actualExistingId3v1Tag) {
1497  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1498  return;
1499  }
1500  progress.updateStep("Removing ID3v1 tag ...");
1501  stream().close();
1502  if (truncate(path().data(), static_cast<std::streamoff>(size() - 128)) == 0) {
1503  reportSizeChanged(size() - 128);
1504  } else {
1505  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1506  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1507  }
1508  return;
1509  } else {
1510  // add or update ID3v1 tag
1511  if (m_actualExistingId3v1Tag) {
1512  progress.updateStep("Updating existing ID3v1 tag ...");
1513  // ensure the file is still open / not readonly
1514  open();
1515  stream().seekp(-128, ios_base::end);
1516  try {
1517  m_id3v1Tag->make(stream(), diag);
1518  } catch (const Failure &) {
1519  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1520  }
1521  } else {
1522  progress.updateStep("Adding new ID3v1 tag ...");
1523  // ensure the file is still open / not readonly
1524  open();
1525  stream().seekp(0, ios_base::end);
1526  try {
1527  m_id3v1Tag->make(stream(), diag);
1528  } catch (const Failure &) {
1529  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1530  }
1531  }
1532  }
1533  return;
1534  }
1535 
1536  // ID3v2 needs to be modified
1537  FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1538  progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1539 
1540  // prepare ID3v2 tags
1541  vector<Id3v2TagMaker> makers;
1542  makers.reserve(m_id3v2Tags.size());
1543  uint32 tagsSize = 0;
1544  for (auto &tag : m_id3v2Tags) {
1545  try {
1546  makers.emplace_back(tag->prepareMaking(diag));
1547  tagsSize += makers.back().requiredSize();
1548  } catch (const Failure &) {
1549  }
1550  }
1551 
1552  // determine stream offset and make track/format specific metadata
1553  uint32 streamOffset; // where the actual stream starts
1554  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1555  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1556  std::streamoff startOfLastMetaDataBlock;
1557  if (flacStream) {
1558  // if it is a raw FLAC stream, make FLAC metadata
1559  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1560  tagsSize += flacMetaData.tellp();
1561  streamOffset = flacStream->streamOffset();
1562  } else {
1563  // make no further metadata, just use the container offset as stream offset
1564  streamOffset = static_cast<uint32>(m_containerOffset);
1565  }
1566 
1567  // check whether rewrite is required
1568  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1569  size_t padding = 0;
1570  if (!rewriteRequired) {
1571  // rewriting is not forced and new tag is not too big for available space
1572  // -> calculate new padding
1573  padding = streamOffset - tagsSize;
1574  // -> check whether the new padding matches specifications
1575  if (padding < minPadding() || padding > maxPadding()) {
1576  rewriteRequired = true;
1577  }
1578  }
1579  if (makers.empty() && !flacStream) {
1580  // an ID3v2 tag is not written and it is not a FLAC stream
1581  // -> can't include padding
1582  if (padding) {
1583  // but padding would be present -> need to rewrite
1584  padding = 0; // can't write the preferred padding despite rewriting
1585  rewriteRequired = true;
1586  }
1587  } else if (rewriteRequired) {
1588  // rewriting is forced or new ID3v2 tag is too big for available space
1589  // -> use preferred padding when rewriting anyways
1590  padding = preferredPadding();
1591  } else if (makers.empty() && flacStream && padding && padding < 4) {
1592  // no ID3v2 tag -> must include padding in FLAC stream
1593  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1594  padding = preferredPadding();
1595  rewriteRequired = true;
1596  }
1597  if (rewriteRequired && flacStream && makers.empty() && padding) {
1598  // the first 4 byte of FLAC padding actually don't count because these
1599  // can not be used for additional meta data
1600  padding += 4;
1601  }
1602  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1603 
1604  // setup stream(s) for writing
1605  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1606  string backupPath;
1607  NativeFileStream &outputStream = stream();
1608  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1609 
1610  if (rewriteRequired) {
1611  if (m_saveFilePath.empty()) {
1612  // move current file to temp dir and reopen it as backupStream, recreate original file
1613  try {
1614  BackupHelper::createBackupFile(backupDirectory(), path(), backupPath, outputStream, backupStream);
1615  // recreate original file, define buffer variables
1616  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1617  } catch (...) {
1618  const char *const what = catchIoFailure();
1619  diag.emplace_back(DiagLevel::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1620  throwIoFailure(what);
1621  }
1622  } else {
1623  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1624  try {
1625  close();
1626  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1627  backupStream.open(path(), ios_base::in | ios_base::binary);
1628  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1629  } catch (...) {
1630  const char *const what = catchIoFailure();
1631  diag.emplace_back(DiagLevel::Critical, "Opening streams to write output file failed.", context);
1632  throwIoFailure(what);
1633  }
1634  }
1635 
1636  } else { // !rewriteRequired
1637  // reopen original file to ensure it is opened for writing
1638  try {
1639  close();
1640  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1641  } catch (...) {
1642  const char *const what = catchIoFailure();
1643  diag.emplace_back(DiagLevel::Critical, "Opening the file with write permissions failed.", context);
1644  throwIoFailure(what);
1645  }
1646  }
1647 
1648  // start actual writing
1649  try {
1650  // ensure we can cast padding safely to uint32
1651  if (padding > numeric_limits<uint32>::max()) {
1652  padding = numeric_limits<uint32>::max();
1653  diag.emplace_back(
1654  DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1655  }
1656 
1657  if (!makers.empty()) {
1658  // write ID3v2 tags
1659  progress.updateStep("Writing ID3v2 tag ...");
1660  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1661  i->make(outputStream, 0, diag);
1662  }
1663  // include padding into the last ID3v2 tag
1664  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<uint32>(padding), diag);
1665  }
1666 
1667  if (flacStream) {
1668  if (padding && startOfLastMetaDataBlock) {
1669  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1670  flacMetaData.seekg(startOfLastMetaDataBlock);
1671  flacMetaData.seekp(startOfLastMetaDataBlock);
1672  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1673  flacMetaData.seekg(0);
1674  }
1675 
1676  // write FLAC metadata
1677  outputStream << flacMetaData.rdbuf();
1678 
1679  // write padding
1680  if (padding) {
1681  flacStream->makePadding(outputStream, static_cast<uint32>(padding), true, diag);
1682  }
1683  }
1684 
1685  if (makers.empty() && !flacStream) {
1686  // just write padding (however, padding should be set to 0 in this case?)
1687  for (; padding; --padding) {
1688  outputStream.put(0);
1689  }
1690  }
1691 
1692  // copy / skip actual stream data
1693  // -> determine media data size
1694  uint64 mediaDataSize = size() - streamOffset;
1695  if (m_actualExistingId3v1Tag) {
1696  mediaDataSize -= 128;
1697  }
1698 
1699  if (rewriteRequired) {
1700  // copy data from original file
1701  switch (m_containerFormat) {
1703  progress.updateStep("Writing MPEG audio frames ...");
1704  break;
1705  default:
1706  progress.updateStep("Writing frames ...");
1707  }
1708  backupStream.seekg(static_cast<streamoff>(streamOffset));
1709  CopyHelper<0x4000> copyHelper;
1710  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1711  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1712  } else {
1713  // just skip actual stream data
1714  outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1715  }
1716 
1717  // write ID3v1 tag
1718  if (m_id3v1Tag) {
1719  progress.updateStep("Writing ID3v1 tag ...");
1720  try {
1721  m_id3v1Tag->make(stream(), diag);
1722  } catch (const Failure &) {
1723  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1724  }
1725  }
1726 
1727  // handle streams
1728  if (rewriteRequired) {
1729  // report new size
1730  reportSizeChanged(static_cast<uint64>(outputStream.tellp()));
1731  // "save as path" is now the regular path
1732  if (!saveFilePath().empty()) {
1734  m_saveFilePath.clear();
1735  }
1736  // stream is useless for further usage because it is write-only
1737  outputStream.close();
1738  } else {
1739  const auto newSize = static_cast<uint64>(outputStream.tellp());
1740  if (newSize < size()) {
1741  // file is smaller after the modification -> truncate
1742  // -> close stream before truncating
1743  outputStream.close();
1744  // -> truncate file
1745  if (truncate(path().c_str(), static_cast<streamoff>(newSize)) == 0) {
1746  reportSizeChanged(newSize);
1747  } else {
1748  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1749  }
1750  } else {
1751  // file is longer after the modification -> just report new size
1752  reportSizeChanged(newSize);
1753  }
1754  }
1755 
1756  } catch (...) {
1757  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1758  }
1759 }
1760 
1761 } // 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.
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:251
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...
bool removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
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.
constexpr TAG_PARSER_EXPORT const char * version()
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.
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:497
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.
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
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
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:249
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:102
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:40
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.
const std::string & backupDirectory() const
Returns the directory used to store backup files.
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.
constexpr TAG_PARSER_EXPORT const char * description()
virtual unsigned int insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition: tag.cpp:86
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:156
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