Tag Parser  8.3.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 "./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 {
885  const auto duration = this->duration();
886  if (duration.isNull()) {
887  return 0.0;
888  }
889  return 0.0078125 * size() / duration.totalSeconds();
890 }
891 
902 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
903 {
904  unordered_set<string> res;
905  if (m_container) {
906  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
907  const AbstractTrack *track = m_container->track(i);
908  if ((type == MediaType::Unknown || track->mediaType() == type) && !track->language().empty() && track->language() != "und") {
909  res.emplace(track->language());
910  }
911  }
912  } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
913  && m_singleTrack->language() != "und") {
914  res.emplace(m_singleTrack->language());
915  }
916  return res;
917 }
918 
931 {
932  if (m_container) {
933  const size_t trackCount = m_container->trackCount();
934  vector<string> parts;
935  parts.reserve(trackCount);
936  for (size_t i = 0; i != trackCount; ++i) {
937  const string description(m_container->track(i)->description());
938  if (!description.empty()) {
939  parts.emplace_back(move(description));
940  }
941  }
942  return joinStrings(parts, " / ");
943  } else if (m_singleTrack) {
944  return m_singleTrack->description();
945  }
946  return string();
947 }
948 
959 {
961  return false;
962  }
963  if (m_id3v1Tag) {
964  m_id3v1Tag.reset();
965  return true;
966  }
967  return false;
968 }
969 
986 {
988  return nullptr;
989  }
990  if (!m_id3v1Tag) {
991  m_id3v1Tag = make_unique<Id3v1Tag>();
992  }
993  return m_id3v1Tag.get();
994 }
995 
1007 {
1009  return false;
1010  }
1011  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1012  if (i->get() == tag) {
1013  m_id3v2Tags.erase(i);
1014  return true;
1015  }
1016  }
1017  return false;
1018 }
1019 
1029 {
1030  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1031  return false;
1032  }
1033  m_id3v2Tags.clear();
1034  return true;
1035 }
1036 
1053 {
1054  if (m_id3v2Tags.empty()) {
1055  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1056  }
1057  return m_id3v2Tags.front().get();
1058 }
1059 
1074 {
1075  if (!tag) {
1076  return false;
1077  }
1078 
1079  // remove tag via container
1080  if (m_container) {
1081  return m_container->removeTag(tag);
1082  }
1083 
1084  // remove tag via track for "single-track" formats
1085  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1086  auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1087  if (flacStream->vorbisComment() == tag) {
1088  return flacStream->removeVorbisComment();
1089  }
1090  }
1091 
1092  // remove ID3 tags
1093  if (m_id3v1Tag.get() == tag) {
1094  m_id3v1Tag.reset();
1095  return true;
1096  }
1097  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1098  if (i->get() == tag) {
1099  m_id3v2Tags.erase(i);
1100  return true;
1101  }
1102  }
1103  return false;
1104 }
1105 
1113 {
1114  if (m_container) {
1115  m_container->removeAllTags();
1116  }
1117  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1118  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1119  }
1120  m_id3v1Tag.reset();
1121  m_id3v2Tags.clear();
1122 }
1123 
1128 {
1129  if (m_container && m_container->chapterCount()) {
1130  return true;
1131  }
1132  switch (m_containerFormat) {
1134  case ContainerFormat::Webm:
1135  return true;
1136  default:
1137  return false;
1138  }
1139 }
1140 
1145 {
1146  if (m_container && m_container->attachmentCount()) {
1147  return true;
1148  }
1149  switch (m_containerFormat) {
1151  case ContainerFormat::Webm:
1152  return true;
1153  default:
1154  return false;
1155  }
1156 }
1157 
1162 {
1163  if (trackCount()) {
1164  return true;
1165  }
1166  switch (m_containerFormat) {
1167  case ContainerFormat::Mp4:
1170  case ContainerFormat::Ogg:
1172  case ContainerFormat::Webm:
1173  return true;
1174  default:
1175  return false;
1176  }
1177 }
1178 
1183 {
1184  switch (m_containerFormat) {
1185  case ContainerFormat::Adts:
1186  case ContainerFormat::Flac:
1189  case ContainerFormat::Mp4:
1190  case ContainerFormat::Ogg:
1192  case ContainerFormat::Webm:
1193  // these container formats are supported
1194  return true;
1195  default:
1196  // the container format is unsupported
1197  // -> an ID3 tag might be already present, in this case the tags are considered supported
1198  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1199  }
1200 }
1201 
1208 {
1209  // simply return the first tag here since MP4 files never contain multiple tags
1210  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1211  && m_container->tagCount() > 0
1212  ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1213  : nullptr;
1214 }
1215 
1222 const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1223 {
1224  // matroska files might contain multiple tags (targeting different scopes)
1225  if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1226  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1227  } else {
1228  static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1229  return empty;
1230  }
1231 }
1232 
1239 {
1240  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1241  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1242  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1243 }
1244 
1250 vector<AbstractChapter *> MediaFileInfo::chapters() const
1251 {
1252  vector<AbstractChapter *> res;
1253  if (m_container) {
1254  const size_t count = m_container->chapterCount();
1255  res.reserve(count);
1256  for (size_t i = 0; i != count; ++i) {
1257  res.push_back(m_container->chapter(i));
1258  }
1259  }
1260  return res;
1261 }
1262 
1268 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1269 {
1270  vector<AbstractAttachment *> res;
1271  if (m_container) {
1272  const size_t count = m_container->attachmentCount();
1273  res.reserve(count);
1274  for (size_t i = 0; i != count; ++i) {
1275  res.push_back(m_container->attachment(i));
1276  }
1277  }
1278  return res;
1279 }
1280 
1293 {
1294  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1295  m_containerFormat = ContainerFormat::Unknown;
1296  m_containerOffset = 0;
1297  m_paddingSize = 0;
1298  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1299  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1300  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1301  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1302  m_id3v1Tag.reset();
1303  m_id3v2Tags.clear();
1304  m_actualId3v2TagOffsets.clear();
1305  m_actualExistingId3v1Tag = false;
1306  m_container.reset();
1307  m_singleTrack.reset();
1308 }
1309 
1327 {
1328  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1329  if (begin == end) {
1330  return;
1331  }
1332  Id3v2Tag &first = **begin;
1333  auto isecond = begin + 1;
1334  if (isecond == end) {
1335  return;
1336  }
1337  for (auto i = isecond; i != end; ++i) {
1338  first.insertFields(**i, false);
1339  }
1340  m_id3v2Tags.erase(isecond, end - 1);
1341 }
1342 
1354 {
1356  return false;
1357  }
1360 }
1361 
1373 {
1375  return false;
1376  }
1379 }
1380 
1396 {
1397  switch (m_containerFormat) {
1398  case ContainerFormat::Ogg:
1399  if (m_container) {
1400  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1401  }
1402  break;
1403  case ContainerFormat::Flac:
1404  if (m_singleTrack) {
1405  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1406  }
1407  break;
1408  default:;
1409  }
1410  return nullptr;
1411 }
1412 
1423 {
1424  switch (m_containerFormat) {
1425  case ContainerFormat::Ogg:
1426  if (m_container) {
1427  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1428  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1429  return hadTags;
1430  }
1431  break;
1432  case ContainerFormat::Flac:
1433  if (m_singleTrack) {
1434  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1435  }
1436  break;
1437  default:;
1438  }
1439  return false;
1440 }
1441 
1450 void MediaFileInfo::tags(vector<Tag *> &tags) const
1451 {
1452  if (hasId3v1Tag()) {
1453  tags.push_back(m_id3v1Tag.get());
1454  }
1455  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1456  tags.push_back(tag.get());
1457  }
1458  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1459  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1460  tags.push_back(vorbisComment);
1461  }
1462  }
1463  if (m_container) {
1464  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1465  tags.push_back(m_container->tag(i));
1466  }
1467  }
1468 }
1469 
1474 {
1475  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1476  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1477 }
1478 
1485 vector<Tag *> MediaFileInfo::tags() const
1486 {
1487  vector<Tag *> res;
1488  tags(res);
1489  return res;
1490 }
1491 
1496 {
1499 }
1500 
1504 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1505 {
1506  static const string context("making MP3/FLAC file");
1507 
1508  // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1509  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1510  && m_containerFormat != ContainerFormat::Flac) {
1511  // alter ID3v1 tag
1512  if (!m_id3v1Tag) {
1513  // remove ID3v1 tag
1514  if (!m_actualExistingId3v1Tag) {
1515  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1516  return;
1517  }
1518  progress.updateStep("Removing ID3v1 tag ...");
1519  stream().close();
1520  if (truncate(path().data(), static_cast<std::streamoff>(size() - 128)) == 0) {
1521  reportSizeChanged(size() - 128);
1522  } else {
1523  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1524  throwIoFailure("Unable to truncate file to remove ID3v1 tag.");
1525  }
1526  return;
1527  } else {
1528  // add or update ID3v1 tag
1529  if (m_actualExistingId3v1Tag) {
1530  progress.updateStep("Updating existing ID3v1 tag ...");
1531  // ensure the file is still open / not readonly
1532  open();
1533  stream().seekp(-128, ios_base::end);
1534  try {
1535  m_id3v1Tag->make(stream(), diag);
1536  } catch (const Failure &) {
1537  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1538  }
1539  } else {
1540  progress.updateStep("Adding new ID3v1 tag ...");
1541  // ensure the file is still open / not readonly
1542  open();
1543  stream().seekp(0, ios_base::end);
1544  try {
1545  m_id3v1Tag->make(stream(), diag);
1546  } catch (const Failure &) {
1547  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1548  }
1549  }
1550  }
1551  return;
1552  }
1553 
1554  // ID3v2 needs to be modified
1555  FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1556  progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1557 
1558  // prepare ID3v2 tags
1559  vector<Id3v2TagMaker> makers;
1560  makers.reserve(m_id3v2Tags.size());
1561  uint32 tagsSize = 0;
1562  for (auto &tag : m_id3v2Tags) {
1563  try {
1564  makers.emplace_back(tag->prepareMaking(diag));
1565  tagsSize += makers.back().requiredSize();
1566  } catch (const Failure &) {
1567  }
1568  }
1569 
1570  // determine stream offset and make track/format specific metadata
1571  uint32 streamOffset; // where the actual stream starts
1572  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1573  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1574  std::streamoff startOfLastMetaDataBlock;
1575  if (flacStream) {
1576  // if it is a raw FLAC stream, make FLAC metadata
1577  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1578  tagsSize += flacMetaData.tellp();
1579  streamOffset = flacStream->streamOffset();
1580  } else {
1581  // make no further metadata, just use the container offset as stream offset
1582  streamOffset = static_cast<uint32>(m_containerOffset);
1583  }
1584 
1585  // check whether rewrite is required
1586  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1587  size_t padding = 0;
1588  if (!rewriteRequired) {
1589  // rewriting is not forced and new tag is not too big for available space
1590  // -> calculate new padding
1591  padding = streamOffset - tagsSize;
1592  // -> check whether the new padding matches specifications
1593  if (padding < minPadding() || padding > maxPadding()) {
1594  rewriteRequired = true;
1595  }
1596  }
1597  if (makers.empty() && !flacStream) {
1598  // an ID3v2 tag is not written and it is not a FLAC stream
1599  // -> can't include padding
1600  if (padding) {
1601  // but padding would be present -> need to rewrite
1602  padding = 0; // can't write the preferred padding despite rewriting
1603  rewriteRequired = true;
1604  }
1605  } else if (rewriteRequired) {
1606  // rewriting is forced or new ID3v2 tag is too big for available space
1607  // -> use preferred padding when rewriting anyways
1608  padding = preferredPadding();
1609  } else if (makers.empty() && flacStream && padding && padding < 4) {
1610  // no ID3v2 tag -> must include padding in FLAC stream
1611  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1612  padding = preferredPadding();
1613  rewriteRequired = true;
1614  }
1615  if (rewriteRequired && flacStream && makers.empty() && padding) {
1616  // the first 4 byte of FLAC padding actually don't count because these
1617  // can not be used for additional meta data
1618  padding += 4;
1619  }
1620  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1621 
1622  // setup stream(s) for writing
1623  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1624  string backupPath;
1625  NativeFileStream &outputStream = stream();
1626  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1627 
1628  if (rewriteRequired) {
1629  if (m_saveFilePath.empty()) {
1630  // move current file to temp dir and reopen it as backupStream, recreate original file
1631  try {
1632  BackupHelper::createBackupFile(backupDirectory(), path(), backupPath, outputStream, backupStream);
1633  // recreate original file, define buffer variables
1634  outputStream.open(path(), ios_base::out | ios_base::binary | ios_base::trunc);
1635  } catch (...) {
1636  const char *const what = catchIoFailure();
1637  diag.emplace_back(DiagLevel::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1638  throwIoFailure(what);
1639  }
1640  } else {
1641  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1642  try {
1643  close();
1644  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1645  backupStream.open(path(), ios_base::in | ios_base::binary);
1646  outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1647  } catch (...) {
1648  const char *const what = catchIoFailure();
1649  diag.emplace_back(DiagLevel::Critical, "Opening streams to write output file failed.", context);
1650  throwIoFailure(what);
1651  }
1652  }
1653 
1654  } else { // !rewriteRequired
1655  // reopen original file to ensure it is opened for writing
1656  try {
1657  close();
1658  outputStream.open(path(), ios_base::in | ios_base::out | ios_base::binary);
1659  } catch (...) {
1660  const char *const what = catchIoFailure();
1661  diag.emplace_back(DiagLevel::Critical, "Opening the file with write permissions failed.", context);
1662  throwIoFailure(what);
1663  }
1664  }
1665 
1666  // start actual writing
1667  try {
1668  // ensure we can cast padding safely to uint32
1669  if (padding > numeric_limits<uint32>::max()) {
1670  padding = numeric_limits<uint32>::max();
1671  diag.emplace_back(
1672  DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1673  }
1674 
1675  if (!makers.empty()) {
1676  // write ID3v2 tags
1677  progress.updateStep("Writing ID3v2 tag ...");
1678  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1679  i->make(outputStream, 0, diag);
1680  }
1681  // include padding into the last ID3v2 tag
1682  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<uint32>(padding), diag);
1683  }
1684 
1685  if (flacStream) {
1686  if (padding && startOfLastMetaDataBlock) {
1687  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1688  flacMetaData.seekg(startOfLastMetaDataBlock);
1689  flacMetaData.seekp(startOfLastMetaDataBlock);
1690  flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1691  flacMetaData.seekg(0);
1692  }
1693 
1694  // write FLAC metadata
1695  outputStream << flacMetaData.rdbuf();
1696 
1697  // write padding
1698  if (padding) {
1699  flacStream->makePadding(outputStream, static_cast<uint32>(padding), true, diag);
1700  }
1701  }
1702 
1703  if (makers.empty() && !flacStream) {
1704  // just write padding (however, padding should be set to 0 in this case?)
1705  for (; padding; --padding) {
1706  outputStream.put(0);
1707  }
1708  }
1709 
1710  // copy / skip actual stream data
1711  // -> determine media data size
1712  uint64 mediaDataSize = size() - streamOffset;
1713  if (m_actualExistingId3v1Tag) {
1714  mediaDataSize -= 128;
1715  }
1716 
1717  if (rewriteRequired) {
1718  // copy data from original file
1719  switch (m_containerFormat) {
1721  progress.updateStep("Writing MPEG audio frames ...");
1722  break;
1723  default:
1724  progress.updateStep("Writing frames ...");
1725  }
1726  backupStream.seekg(static_cast<streamoff>(streamOffset));
1727  CopyHelper<0x4000> copyHelper;
1728  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1729  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1730  } else {
1731  // just skip actual stream data
1732  outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1733  }
1734 
1735  // write ID3v1 tag
1736  if (m_id3v1Tag) {
1737  progress.updateStep("Writing ID3v1 tag ...");
1738  try {
1739  m_id3v1Tag->make(stream(), diag);
1740  } catch (const Failure &) {
1741  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1742  }
1743  }
1744 
1745  // handle streams
1746  if (rewriteRequired) {
1747  // report new size
1748  reportSizeChanged(static_cast<uint64>(outputStream.tellp()));
1749  // "save as path" is now the regular path
1750  if (!saveFilePath().empty()) {
1752  m_saveFilePath.clear();
1753  }
1754  // stream is useless for further usage because it is write-only
1755  outputStream.close();
1756  } else {
1757  const auto newSize = static_cast<uint64>(outputStream.tellp());
1758  if (newSize < size()) {
1759  // file is smaller after the modification -> truncate
1760  // -> close stream before truncating
1761  outputStream.close();
1762  // -> truncate file
1763  if (truncate(path().c_str(), static_cast<streamoff>(newSize)) == 0) {
1764  reportSizeChanged(newSize);
1765  } else {
1766  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1767  }
1768  } else {
1769  // file is longer after the modification -> just report new size
1770  reportSizeChanged(newSize);
1771  }
1772  }
1773 
1774  } catch (...) {
1775  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1776  }
1777 }
1778 
1779 } // 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:60
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:98
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.
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
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: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....
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.
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 (e....
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,...
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's tracks.
double overallAverageBitrate() const
Returns the overall average bitrate in kbit/s of the file if known; otherwise returns 0....
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 if known; otherwise returns a TimeSpan with zero ticks.
The BasicFileInfo class provides basic file information such as file name, extension,...
Definition: basicfileinfo.h:13