Tag Parser  10.0.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1 #include "./mediafileinfo.h"
2 #include "./abstracttrack.h"
3 #include "./backuphelper.h"
4 #include "./diagnostics.h"
5 #include "./exceptions.h"
6 #include "./locale.h"
7 #include "./progressfeedback.h"
8 #include "./signature.h"
9 #include "./tag.h"
10 
11 #include "./id3/id3v1tag.h"
12 #include "./id3/id3v2tag.h"
13 
14 #include "./wav/waveaudiostream.h"
15 
17 
18 #include "./adts/adtsstream.h"
19 
20 #include "./ivf/ivfstream.h"
21 
22 #include "./mp4/mp4atom.h"
23 #include "./mp4/mp4container.h"
24 #include "./mp4/mp4ids.h"
25 #include "./mp4/mp4tag.h"
26 #include "./mp4/mp4track.h"
27 
28 #include "./matroska/ebmlelement.h"
30 #include "./matroska/matroskatag.h"
32 
33 #include "./ogg/oggcontainer.h"
34 
35 #include "./flac/flacmetadata.h"
36 #include "./flac/flacstream.h"
37 
38 #include <c++utilities/chrono/timespan.h>
39 #include <c++utilities/conversion/stringconversion.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 CppUtilities;
54 
60 namespace TagParser {
61 
77 MediaFileInfo::MediaFileInfo(std::string &&path)
78  : BasicFileInfo(std::move(path))
79  , m_containerParsingStatus(ParsingStatus::NotParsedYet)
80  , m_containerFormat(ContainerFormat::Unknown)
81  , m_containerOffset(0)
82  , m_paddingSize(0)
83  , m_fileStructureFlags(MediaFileStructureFlags::None)
84  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
85  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
86  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
87  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
88  , m_minPadding(0)
89  , m_maxPadding(0)
90  , m_preferredPadding(0)
91  , m_tagPosition(ElementPosition::BeforeData)
92  , m_indexPosition(ElementPosition::BeforeData)
93  , m_fileHandlingFlags(
95 {
96 }
97 
102  : MediaFileInfo(std::string())
103 {
104 }
105 
109 MediaFileInfo::MediaFileInfo(std::string_view path)
110  : MediaFileInfo(std::string(path))
111 {
112 }
113 
118 {
119 }
120 
135 {
136  CPP_UTILITIES_UNUSED(progress)
137 
138  // skip if container format already parsed
140  return;
141  }
142 
143  static const string context("parsing file header");
144  open(); // ensure the file is open
145  m_containerFormat = ContainerFormat::Unknown;
146 
147  // file size
148  m_paddingSize = 0;
149  m_containerOffset = 0;
150  std::size_t bytesSkippedBeforeContainer = 0;
151 
152  // read signatrue
153  char buff[16];
154  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
155 startParsingSignature:
156  if (progress.isAborted()) {
157  diag.emplace_back(DiagLevel::Information, "Parsing the container format has been aborted.", context);
158  return;
159  }
160  if (size() - containerOffset() >= 16) {
161  stream().seekg(m_containerOffset, ios_base::beg);
162  stream().read(buff, sizeof(buff));
163 
164  // skip zero/junk bytes
165  // notes:
166  // - Only skipping 4 or more consecutive zero bytes at this point because some signatures start with up to 4 zero bytes.
167  // - It seems that most players/tools¹ skip junk bytes, at least when reading MP3 files. Hence the tagparser library is following
168  // the same approach. (¹e.g. ffmpeg: "[mp3 @ 0x559e1f4cbd80] Skipping 1670 bytes of junk at 1165.")
169  std::size_t bytesSkipped = 0;
170  for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
171  ;
172  if (bytesSkipped >= 4) {
173  skipJunkBytes:
174  m_containerOffset += static_cast<std::streamoff>(bytesSkipped);
175  m_paddingSize += bytesSkipped;
176 
177  // give up after 0x800 bytes
178  if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
179  m_containerParsingStatus = ParsingStatus::NotSupported;
180  m_containerFormat = ContainerFormat::Unknown;
181  return;
182  }
183 
184  // try again
185  goto startParsingSignature;
186  }
187 
188  // parse signature
189  switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
191  // save position of ID3v2 tag
192  m_actualId3v2TagOffsets.push_back(m_containerOffset);
193  if (m_actualId3v2TagOffsets.size() == 2) {
194  diag.emplace_back(DiagLevel::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
195  }
196 
197  // read ID3v2 header
198  stream().seekg(m_containerOffset + 5, ios_base::beg);
199  stream().read(buff, 5);
200 
201  // set the container offset to skip ID3v2 header
202  m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
203  if ((*buff) & 0x10) {
204  // footer present
205  m_containerOffset += 10;
206  }
207 
208  // continue reading signature
209  goto startParsingSignature;
210 
213  // MP4/QuickTime is handled using Mp4Container instance
214  m_container = make_unique<Mp4Container>(*this, m_containerOffset);
215  try {
216  static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, progress, &m_paddingSize);
217  } catch (const OperationAbortedException &) {
218  diag.emplace_back(DiagLevel::Information, "Validating the MP4 element structure has been aborted.", context);
219  } catch (const Failure &) {
220  m_containerParsingStatus = ParsingStatus::CriticalFailure;
221  }
222  break;
223  }
224  case ContainerFormat::Ebml: {
225  // EBML/Matroska is handled using MatroskaContainer instance
226  auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
227  try {
228  container->parseHeader(diag, progress);
229  if (container->documentType() == "matroska") {
230  m_containerFormat = ContainerFormat::Matroska;
231  } else if (container->documentType() == "webm") {
232  m_containerFormat = ContainerFormat::Webm;
233  }
234  if (isForcingFullParse()) {
235  // validating the element structure of Matroska files takes too long when
236  // parsing big files so do this only when explicitely desired
237  container->validateElementStructure(diag, progress, &m_paddingSize);
238  container->validateIndex(diag, progress);
239  }
240  } catch (const OperationAbortedException &) {
241  diag.emplace_back(DiagLevel::Information, "Validating the Matroska element structure has been aborted.", context);
242  } catch (const Failure &) {
243  m_containerParsingStatus = ParsingStatus::CriticalFailure;
244  }
245  m_container = move(container);
246  break;
247  }
249  // Ogg is handled by OggContainer instance
250  m_container = make_unique<OggContainer>(*this, m_containerOffset);
251  static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(isForcingFullParse());
252  break;
254  // check for magic numbers at odd offsets
255  // -> check for tar (magic number at offset 0x101)
256  if (size() > 0x107) {
257  stream().seekg(0x101);
258  stream().read(buff, 6);
259  if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
260  m_containerFormat = ContainerFormat::Tar;
261  break;
262  }
263  }
264  // skip previously determined zero-bytes or try our luck on the next byte
265  if (!bytesSkipped) {
266  ++bytesSkipped;
267  }
268  goto skipJunkBytes;
269  default:;
270  }
271  }
272 
273  if (bytesSkippedBeforeContainer) {
274  diag.emplace_back(DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer, " bytes of junk skipped"), context);
275  }
276 
277  // set parsing status
278  if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
279  if (m_containerFormat == ContainerFormat::Unknown) {
280  m_containerParsingStatus = ParsingStatus::NotSupported;
281  } else {
282  m_containerParsingStatus = ParsingStatus::Ok;
283  }
284  }
285 }
286 
299 {
300  // skip if tracks already parsed
302  return;
303  }
304  static const string context("parsing tracks");
305 
306  try {
307  // parse tracks via container object
308  if (m_container) {
309  m_container->parseTracks(diag, progress);
310  m_tracksParsingStatus = ParsingStatus::Ok;
311  return;
312  }
313 
314  // parse tracks via track object for "single-track"-formats
315  switch (m_containerFormat) {
317  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
318  break;
320  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
321  break;
323  m_singleTrack = make_unique<IvfStream>(stream(), m_containerOffset);
324  break;
326  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
327  break;
329  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
330  break;
331  default:
332  throw NotImplementedException();
333  }
334  m_singleTrack->parseHeader(diag, progress);
335 
336  // take padding for some "single-track" formats into account
337  switch (m_containerFormat) {
339  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
340  break;
341  default:;
342  }
343 
344  m_tracksParsingStatus = ParsingStatus::Ok;
345 
346  } catch (const NotImplementedException &) {
347  diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
348  m_tracksParsingStatus = ParsingStatus::NotSupported;
349  } catch (const OperationAbortedException &) {
350  diag.emplace_back(DiagLevel::Information, "Parsing tracks has been aborted.", context);
351  } catch (const Failure &) {
352  diag.emplace_back(DiagLevel::Critical, "Unable to parse tracks.", context);
353  m_tracksParsingStatus = ParsingStatus::CriticalFailure;
354  }
355 }
356 
370 {
371  // skip if tags already parsed
373  return;
374  }
375  static const string context("parsing tag");
376 
377  // check for ID3v1 tag
378  if (size() >= 128) {
379  m_id3v1Tag = make_unique<Id3v1Tag>();
380  try {
381  stream().seekg(-128, ios_base::end);
382  m_id3v1Tag->parse(stream(), diag);
383  m_fileStructureFlags += MediaFileStructureFlags::ActualExistingId3v1Tag;
384  } catch (const NoDataFoundException &) {
385  m_id3v1Tag.reset();
386  } catch (const OperationAbortedException &) {
387  diag.emplace_back(DiagLevel::Information, "Parsing ID3v1 tag has been aborted.", context);
388  return;
389  } catch (const Failure &) {
390  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
391  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
392  }
393  }
394 
395  // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
396  m_id3v2Tags.clear();
397  for (const auto offset : m_actualId3v2TagOffsets) {
398  auto id3v2Tag = make_unique<Id3v2Tag>();
399  stream().seekg(offset, ios_base::beg);
400  try {
401  id3v2Tag->parse(stream(), size() - static_cast<std::uint64_t>(offset), diag);
402  m_paddingSize += id3v2Tag->paddingSize();
403  } catch (const NoDataFoundException &) {
404  continue;
405  } catch (const OperationAbortedException &) {
406  diag.emplace_back(DiagLevel::Information, "Parsing ID3v2 tags has been aborted.", context);
407  return;
408  } catch (const Failure &) {
409  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
410  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v2 tag.", context);
411  }
412  m_id3v2Tags.emplace_back(id3v2Tag.release());
413  }
414 
415  // check for tags in tracks (FLAC only) or via container object
416  try {
417  if (m_containerFormat == ContainerFormat::Flac) {
418  parseTracks(diag, progress);
419  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
420  m_tagsParsingStatus = m_tracksParsingStatus;
421  }
422  return;
423  } else if (m_container) {
424  m_container->parseTags(diag, progress);
425  } else {
426  throw NotImplementedException();
427  }
428 
429  // set status, but do not override error/unsupported status form ID3 tags here
430  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
431  m_tagsParsingStatus = ParsingStatus::Ok;
432  }
433 
434  } catch (const NotImplementedException &) {
435  // set status to not supported, but do not override parsing status from ID3 tags here
436  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
437  m_tagsParsingStatus = ParsingStatus::NotSupported;
438  }
439  diag.emplace_back(DiagLevel::Information, "Parsing tags is not implemented for the container format of the file.", context);
440  } catch (const OperationAbortedException &) {
441  diag.emplace_back(DiagLevel::Information, "Parsing tags from container/streams has been aborted.", context);
442  return;
443  } catch (const Failure &) {
444  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
445  diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
446  }
447 }
448 
459 {
460  // skip if chapters already parsed
462  return;
463  }
464  static const string context("parsing chapters");
465 
466  try {
467  // parse chapters via container object
468  if (!m_container) {
469  throw NotImplementedException();
470  }
471  m_container->parseChapters(diag, progress);
472  m_chaptersParsingStatus = ParsingStatus::Ok;
473  } catch (const NotImplementedException &) {
474  m_chaptersParsingStatus = ParsingStatus::NotSupported;
475  diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
476  } catch (const Failure &) {
477  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
478  diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
479  }
480 }
481 
492 {
493  // skip if attachments already parsed
495  return;
496  }
497  static const string context("parsing attachments");
498 
499  try {
500  // parse attachments via container object
501  if (!m_container) {
502  throw NotImplementedException();
503  }
504  m_container->parseAttachments(diag, progress);
505  m_attachmentsParsingStatus = ParsingStatus::Ok;
506  } catch (const NotImplementedException &) {
507  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
508  diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
509  } catch (const Failure &) {
510  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
511  diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
512  }
513 }
514 
522 {
523  parseContainerFormat(diag, progress);
524  if (progress.isAborted()) {
525  return;
526  }
527  parseTracks(diag, progress);
528  if (progress.isAborted()) {
529  return;
530  }
531  parseTags(diag, progress);
532  if (progress.isAborted()) {
533  return;
534  }
535  parseChapters(diag, progress);
536  if (progress.isAborted()) {
537  return;
538  }
539  parseAttachments(diag, progress);
540 }
541 
554 {
555  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
557  return false;
558  }
559 
560  // check if tags need to be created/adjusted/removed
561  const auto requiredTargets(settings.requiredTargets);
562  const auto flags(settings.flags);
563  const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
564  auto targetsSupported = false;
565  if (areTagsSupported() && m_container) {
566  // container object takes care of tag management
567  if (targetsRequired) {
568  // check whether container supports targets
569  if (m_container->tagCount()) {
570  // all tags in the container should support targets if the first one supports targets
571  targetsSupported = m_container->tag(0)->supportsTarget();
572  } else {
573  // try to create a new tag and check whether targets are supported
574  auto *const tag = m_container->createTag();
575  if (tag && (targetsSupported = tag->supportsTarget())) {
576  tag->setTarget(requiredTargets.front());
577  }
578  }
579  if (targetsSupported) {
580  for (const auto &target : requiredTargets) {
581  m_container->createTag(target);
582  }
583  }
584  } else {
585  // no targets are required -> just ensure that at least one tag is present
586  m_container->createTag();
587  }
588  return true;
589  }
590 
591  // no container object present
592  switch (m_containerFormat) {
594  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
595  break;
596  default:
597  // create ID3 tag(s)
599  switch (containerFormat()) {
603  break;
604  default:
605  return false;
606  }
607  }
608  // create ID3 tags according to id3v2usage and id3v2usage
609  // always create ID3v1 tag -> ensure there is one
610  if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
611  auto *const id3v1Tag = createId3v1Tag();
612  if (flags & TagCreationFlags::Id3InitOnCreate) {
613  for (const auto &id3v2Tag : id3v2Tags()) {
614  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
615  id3v1Tag->insertValues(*id3v2Tag, true);
616  // ID3v1 does not support all text encodings which might be used in ID3v2
618  }
619  }
620  }
621  if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
622  // always create ID3v2 tag -> ensure there is one and set version
623  auto *const id3v2Tag = createId3v2Tag();
624  id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
625  if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
626  id3v2Tag->insertValues(*id3v1Tag(), true);
627  }
628  }
629  }
630 
632  mergeId3v2Tags();
633  }
634  // remove ID3 tags according to settings
635  if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
636  // transfer tags to ID3v2 tag before removing
638  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
639  }
640  removeId3v1Tag();
641  }
642  if (settings.id3v2usage == TagUsage::Never) {
644  // transfer tags to ID3v1 tag before removing
645  for (const auto &tag : id3v2Tags()) {
646  id3v1Tag()->insertValues(*tag, false);
647  }
648  }
650  } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
651  // set version of ID3v2 tag according user preferences
652  for (const auto &tag : id3v2Tags()) {
653  tag->setVersion(settings.id3v2MajorVersion, 0);
654  }
655  }
656  return true;
657 }
658 
680 {
681  static const string context("making file");
682  diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
683  bool previousParsingSuccessful = true;
684  switch (tagsParsingStatus()) {
685  case ParsingStatus::Ok:
687  break;
688  default:
689  previousParsingSuccessful = false;
690  diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
691  }
692  switch (tracksParsingStatus()) {
693  case ParsingStatus::Ok:
695  break;
696  default:
697  previousParsingSuccessful = false;
698  diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
699  }
700  if (!previousParsingSuccessful) {
701  throw InvalidDataException();
702  }
703  if (m_container) { // container object takes care
704  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
705  if (hasId3v1Tag()) {
706  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
707  }
708  if (hasId3v2Tag()) {
709  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
710  }
711  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
712  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
713  try {
714  m_container->makeFile(diag, progress);
715  } catch (...) {
716  // since the file might be messed up, invalidate the parsing results
718  throw;
719  }
720  } else { // implementation if no container object is present
721  // assume the file is a MP3 file
722  try {
723  makeMp3File(diag, progress);
724  } catch (...) {
725  // since the file might be messed up, invalidate the parsing results
727  throw;
728  }
729  }
731 }
732 
746 {
747  MediaType mediaType = MediaType::Unknown;
748  unsigned int version = 0;
749  switch (m_containerFormat) {
750  case ContainerFormat::Ogg: {
751  // check for video track or whether only Opus or Speex tracks are present
752  const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
753  if (tracks.empty()) {
754  break;
755  }
756  bool onlyOpus = true, onlySpeex = true;
757  for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
758  if (track->mediaType() == MediaType::Video) {
759  mediaType = MediaType::Video;
760  }
761  if (track->format().general != GeneralMediaFormat::Opus) {
762  onlyOpus = false;
763  }
764  if (track->format().general != GeneralMediaFormat::Speex) {
765  onlySpeex = false;
766  }
767  }
768  if (onlyOpus) {
769  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
770  } else if (onlySpeex) {
771  version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
772  }
773  break;
774  }
778  break;
780  if (m_singleTrack) {
781  version = m_singleTrack->format().sub;
782  }
783  break;
784  default:;
785  }
786  return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
787 }
788 
799 string_view MediaFileInfo::mimeType() const
800 {
801  MediaType mediaType;
802  switch (m_containerFormat) {
807  break;
808  default:
809  mediaType = MediaType::Unknown;
810  }
811  return TagParser::containerMimeType(m_containerFormat, mediaType);
812 }
813 
826 vector<AbstractTrack *> MediaFileInfo::tracks() const
827 {
828  vector<AbstractTrack *> res;
829  size_t trackCount = 0;
830  size_t containerTrackCount = 0;
831  if (m_singleTrack) {
832  trackCount = 1;
833  }
834  if (m_container) {
835  trackCount += (containerTrackCount = m_container->trackCount());
836  }
837  res.reserve(trackCount);
838 
839  if (m_singleTrack) {
840  res.push_back(m_singleTrack.get());
841  }
842  for (size_t i = 0; i != containerTrackCount; ++i) {
843  res.push_back(m_container->track(i));
844  }
845  return res;
846 }
847 
857 {
859  return false;
860  }
861  if (m_singleTrack && m_singleTrack->mediaType() == type) {
862  return true;
863  } else if (m_container) {
864  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
865  if (m_container->track(i)->mediaType() == type) {
866  return true;
867  }
868  }
869  }
870  return false;
871 }
872 
882 CppUtilities::TimeSpan MediaFileInfo::duration() const
883 {
884  if (m_container) {
885  return m_container->duration();
886  } else if (m_singleTrack) {
887  return m_singleTrack->duration();
888  }
889  return TimeSpan();
890 }
891 
902 {
903  const auto duration = this->duration();
904  if (duration.isNull()) {
905  return 0.0;
906  }
907  return 0.0078125 * static_cast<double>(size()) / duration.totalSeconds();
908 }
909 
920 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
921 {
922  unordered_set<string> res;
923  if (m_container) {
924  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
925  const AbstractTrack *const track = m_container->track(i);
926  if (type != MediaType::Unknown && track->mediaType() != type) {
927  continue;
928  }
929  if (const auto &language = track->locale().someAbbreviatedName(); !language.empty()) {
930  res.emplace(language);
931  }
932  }
933  } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type)) {
934  if (const auto &language = m_singleTrack->locale().someAbbreviatedName(); !language.empty()) {
935  res.emplace(language);
936  }
937  }
938  return res;
939 }
940 
953 {
954  if (m_container) {
955  const size_t trackCount = m_container->trackCount();
956  vector<string> parts;
957  parts.reserve(trackCount);
958  for (size_t i = 0; i != trackCount; ++i) {
959  const string description(m_container->track(i)->description());
960  if (!description.empty()) {
961  parts.emplace_back(move(description));
962  }
963  }
964  return joinStrings(parts, " / ");
965  } else if (m_singleTrack) {
966  return m_singleTrack->description();
967  }
968  return string();
969 }
970 
981 {
983  return false;
984  }
985  if (m_id3v1Tag) {
986  m_id3v1Tag.reset();
987  return true;
988  }
989  return false;
990 }
991 
1008 {
1010  return nullptr;
1011  }
1012  if (!m_id3v1Tag) {
1013  m_id3v1Tag = make_unique<Id3v1Tag>();
1014  }
1015  return m_id3v1Tag.get();
1016 }
1017 
1029 {
1031  return false;
1032  }
1033  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1034  if (i->get() == tag) {
1035  m_id3v2Tags.erase(i);
1036  return true;
1037  }
1038  }
1039  return false;
1040 }
1041 
1051 {
1052  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1053  return false;
1054  }
1055  m_id3v2Tags.clear();
1056  return true;
1057 }
1058 
1075 {
1076  if (m_id3v2Tags.empty()) {
1077  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1078  }
1079  return m_id3v2Tags.front().get();
1080 }
1081 
1096 {
1097  if (!tag) {
1098  return false;
1099  }
1100 
1101  // remove tag via container
1102  if (m_container) {
1103  return m_container->removeTag(tag);
1104  }
1105 
1106  // remove tag via track for "single-track" formats
1107  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1108  auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1109  if (flacStream->vorbisComment() == tag) {
1110  return flacStream->removeVorbisComment();
1111  }
1112  }
1113 
1114  // remove ID3 tags
1115  if (m_id3v1Tag.get() == tag) {
1116  m_id3v1Tag.reset();
1117  return true;
1118  }
1119  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1120  if (i->get() == tag) {
1121  m_id3v2Tags.erase(i);
1122  return true;
1123  }
1124  }
1125  return false;
1126 }
1127 
1135 {
1136  if (m_container) {
1137  m_container->removeAllTags();
1138  }
1139  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1140  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1141  }
1142  m_id3v1Tag.reset();
1143  m_id3v2Tags.clear();
1144 }
1145 
1150 {
1151  if (m_container && m_container->chapterCount()) {
1152  return true;
1153  }
1154  switch (m_containerFormat) {
1156  case ContainerFormat::Webm:
1157  return true;
1158  default:
1159  return false;
1160  }
1161 }
1162 
1167 {
1168  if (m_container && m_container->attachmentCount()) {
1169  return true;
1170  }
1171  switch (m_containerFormat) {
1173  case ContainerFormat::Webm:
1174  return true;
1175  default:
1176  return false;
1177  }
1178 }
1179 
1184 {
1185  if (trackCount()) {
1186  return true;
1187  }
1188  switch (m_containerFormat) {
1189  case ContainerFormat::Mp4:
1192  case ContainerFormat::Ogg:
1194  case ContainerFormat::Webm:
1195  return true;
1196  default:
1197  return false;
1198  }
1199 }
1200 
1205 {
1206  switch (m_containerFormat) {
1207  case ContainerFormat::Adts:
1208  case ContainerFormat::Flac:
1211  case ContainerFormat::Mp4:
1212  case ContainerFormat::Ogg:
1214  case ContainerFormat::Webm:
1215  // these container formats are supported
1216  return true;
1217  default:
1218  // the container format is unsupported
1219  // -> an ID3 tag might be already present, in this case the tags are considered supported
1220  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1221  }
1222 }
1223 
1230 {
1231  // simply return the first tag here since MP4 files never contain multiple tags
1232  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1233  && m_container->tagCount() > 0
1234  ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1235  : nullptr;
1236 }
1237 
1244 const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1245 {
1246  // matroska files might contain multiple tags (targeting different scopes)
1247  if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1248  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1249  } else {
1250  static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1251  return empty;
1252  }
1253 }
1254 
1261 {
1262  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1263  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1264  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1265 }
1266 
1272 vector<AbstractChapter *> MediaFileInfo::chapters() const
1273 {
1274  vector<AbstractChapter *> res;
1275  if (m_container) {
1276  const size_t count = m_container->chapterCount();
1277  res.reserve(count);
1278  for (size_t i = 0; i != count; ++i) {
1279  res.push_back(m_container->chapter(i));
1280  }
1281  }
1282  return res;
1283 }
1284 
1290 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1291 {
1292  vector<AbstractAttachment *> res;
1293  if (m_container) {
1294  const size_t count = m_container->attachmentCount();
1295  res.reserve(count);
1296  for (size_t i = 0; i != count; ++i) {
1297  res.push_back(m_container->attachment(i));
1298  }
1299  }
1300  return res;
1301 }
1302 
1315 {
1316  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1317  m_containerFormat = ContainerFormat::Unknown;
1318  m_containerOffset = 0;
1319  m_paddingSize = 0;
1320  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1321  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1322  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1323  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1324  m_id3v1Tag.reset();
1325  m_id3v2Tags.clear();
1326  m_actualId3v2TagOffsets.clear();
1327  m_fileStructureFlags = MediaFileStructureFlags::None;
1328  m_container.reset();
1329  m_singleTrack.reset();
1330 }
1331 
1349 {
1350  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1351  if (begin == end) {
1352  return;
1353  }
1354  Id3v2Tag &first = **begin;
1355  auto isecond = begin + 1;
1356  if (isecond == end) {
1357  return;
1358  }
1359  for (auto i = isecond; i != end; ++i) {
1360  first.insertFields(**i, false);
1361  }
1362  m_id3v2Tags.erase(isecond, end - 1);
1363 }
1364 
1376 {
1378  return false;
1379  }
1382 }
1383 
1395 {
1397  return false;
1398  }
1401 }
1402 
1418 {
1419  switch (m_containerFormat) {
1420  case ContainerFormat::Ogg:
1421  if (m_container) {
1422  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1423  }
1424  break;
1425  case ContainerFormat::Flac:
1426  if (m_singleTrack) {
1427  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1428  }
1429  break;
1430  default:;
1431  }
1432  return nullptr;
1433 }
1434 
1445 {
1446  switch (m_containerFormat) {
1447  case ContainerFormat::Ogg:
1448  if (m_container) {
1449  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1450  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1451  return hadTags;
1452  }
1453  break;
1454  case ContainerFormat::Flac:
1455  if (m_singleTrack) {
1456  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1457  }
1458  break;
1459  default:;
1460  }
1461  return false;
1462 }
1463 
1472 void MediaFileInfo::tags(vector<Tag *> &tags) const
1473 {
1474  if (hasId3v1Tag()) {
1475  tags.push_back(m_id3v1Tag.get());
1476  }
1477  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1478  tags.push_back(tag.get());
1479  }
1480  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1481  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1482  tags.push_back(vorbisComment);
1483  }
1484  }
1485  if (m_container) {
1486  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1487  tags.push_back(m_container->tag(i));
1488  }
1489  }
1490 }
1491 
1496 {
1497  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1498  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1499 }
1500 
1507 vector<Tag *> MediaFileInfo::tags() const
1508 {
1509  vector<Tag *> res;
1510  tags(res);
1511  return res;
1512 }
1513 
1518 {
1521 }
1522 
1526 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1527 {
1528  static const string context("making MP3/FLAC file");
1529 
1530  // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1531  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1532  && m_containerFormat != ContainerFormat::Flac) {
1533  // alter ID3v1 tag
1534  if (!m_id3v1Tag) {
1535  // remove ID3v1 tag
1536  if (!(m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag)) {
1537  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1538  return;
1539  }
1540  progress.updateStep("Removing ID3v1 tag ...");
1541  stream().close();
1542  if (truncate(BasicFileInfo::pathForOpen(path()).data(), static_cast<std::streamoff>(size() - 128)) == 0) {
1543  reportSizeChanged(size() - 128);
1544  } else {
1545  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1546  throw std::ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
1547  }
1548  return;
1549  } else {
1550  // add or update ID3v1 tag
1551  if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1552  progress.updateStep("Updating existing ID3v1 tag ...");
1553  // ensure the file is still open / not readonly
1554  open();
1555  stream().seekp(-128, ios_base::end);
1556  try {
1557  m_id3v1Tag->make(stream(), diag);
1558  } catch (const Failure &) {
1559  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1560  }
1561  } else {
1562  progress.updateStep("Adding new ID3v1 tag ...");
1563  // ensure the file is still open / not readonly
1564  open();
1565  stream().seekp(0, ios_base::end);
1566  try {
1567  m_id3v1Tag->make(stream(), diag);
1568  } catch (const Failure &) {
1569  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1570  }
1571  }
1572  // prevent deferring final write operations (to catch and handle possible errors here)
1573  stream().flush();
1574  }
1575  return;
1576  }
1577 
1578  // ID3v2 needs to be modified
1579  FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1580  progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1581 
1582  // prepare ID3v2 tags
1583  vector<Id3v2TagMaker> makers;
1584  makers.reserve(m_id3v2Tags.size());
1585  std::uint64_t tagsSize = 0;
1586  for (auto &tag : m_id3v2Tags) {
1587  try {
1588  makers.emplace_back(tag->prepareMaking(diag));
1589  tagsSize += makers.back().requiredSize();
1590  } catch (const Failure &) {
1591  }
1592  }
1593 
1594  // determine stream offset and make track/format specific metadata
1595  std::uint32_t streamOffset; // where the actual stream starts
1596  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1597  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1598  std::streamoff startOfLastMetaDataBlock;
1599  if (flacStream) {
1600  // if it is a raw FLAC stream, make FLAC metadata
1601  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1602  tagsSize += static_cast<std::uint64_t>(flacMetaData.tellp());
1603  streamOffset = flacStream->streamOffset();
1604  } else {
1605  // make no further metadata, just use the container offset as stream offset
1606  streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1607  }
1608 
1609  // check whether rewrite is required
1610  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1611  size_t padding = 0;
1612  if (!rewriteRequired) {
1613  // rewriting is not forced and new tag is not too big for available space
1614  // -> calculate new padding
1615  padding = streamOffset - tagsSize;
1616  // -> check whether the new padding matches specifications
1617  if (padding < minPadding() || padding > maxPadding()) {
1618  rewriteRequired = true;
1619  }
1620  }
1621  if (makers.empty() && !flacStream) {
1622  // an ID3v2 tag is not written and it is not a FLAC stream
1623  // -> can't include padding
1624  if (padding) {
1625  // but padding would be present -> need to rewrite
1626  padding = 0; // can't write the preferred padding despite rewriting
1627  rewriteRequired = true;
1628  }
1629  } else if (rewriteRequired) {
1630  // rewriting is forced or new ID3v2 tag is too big for available space
1631  // -> use preferred padding when rewriting anyways
1632  padding = preferredPadding();
1633  } else if (makers.empty() && flacStream && padding && padding < 4) {
1634  // no ID3v2 tag -> must include padding in FLAC stream
1635  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1636  padding = preferredPadding();
1637  rewriteRequired = true;
1638  }
1639  if (rewriteRequired && flacStream && makers.empty() && padding) {
1640  // the first 4 byte of FLAC padding actually don't count because these
1641  // can not be used for additional meta data
1642  padding += 4;
1643  }
1644  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1645 
1646  // setup stream(s) for writing
1647  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1648  string backupPath;
1649  NativeFileStream &outputStream = stream();
1650  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1651 
1652  if (rewriteRequired) {
1653  if (m_saveFilePath.empty()) {
1654  // move current file to temp dir and reopen it as backupStream, recreate original file
1655  try {
1656  BackupHelper::createBackupFile(backupDirectory(), path(), backupPath, outputStream, backupStream);
1657  // recreate original file, define buffer variables
1658  outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1659  } catch (const std::ios_base::failure &failure) {
1660  diag.emplace_back(
1661  DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1662  throw;
1663  }
1664  } else {
1665  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1666  try {
1667  close();
1668  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1669  backupStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::binary);
1670  outputStream.open(BasicFileInfo::pathForOpen(m_saveFilePath).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1671  } catch (const std::ios_base::failure &failure) {
1672  diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1673  throw;
1674  }
1675  }
1676 
1677  } else { // !rewriteRequired
1678  // reopen original file to ensure it is opened for writing
1679  try {
1680  close();
1681  outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::out | ios_base::binary);
1682  } catch (const std::ios_base::failure &failure) {
1683  diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1684  throw;
1685  }
1686  }
1687  // TODO: fix code duplication
1688 
1689  // start actual writing
1690  try {
1691  // ensure we can cast padding safely to uint32
1692  if (padding > numeric_limits<std::uint32_t>::max()) {
1693  padding = numeric_limits<std::uint32_t>::max();
1694  diag.emplace_back(
1695  DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1696  }
1697 
1698  if (!makers.empty()) {
1699  // write ID3v2 tags
1700  progress.updateStep("Writing ID3v2 tag ...");
1701  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1702  i->make(outputStream, 0, diag);
1703  }
1704  // include padding into the last ID3v2 tag
1705  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1706  }
1707 
1708  if (flacStream) {
1709  if (padding && startOfLastMetaDataBlock) {
1710  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1711  flacMetaData.seekg(startOfLastMetaDataBlock);
1712  flacMetaData.seekp(startOfLastMetaDataBlock);
1713  flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1714  flacMetaData.seekg(0);
1715  }
1716 
1717  // write FLAC metadata
1718  outputStream << flacMetaData.rdbuf();
1719 
1720  // write padding
1721  if (padding) {
1722  flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding), true, diag);
1723  }
1724  }
1725 
1726  if (makers.empty() && !flacStream) {
1727  // just write padding (however, padding should be set to 0 in this case?)
1728  for (; padding; --padding) {
1729  outputStream.put(0);
1730  }
1731  }
1732 
1733  // copy / skip actual stream data
1734  // -> determine media data size
1735  std::uint64_t mediaDataSize = size() - streamOffset;
1736  if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1737  mediaDataSize -= 128;
1738  }
1739 
1740  if (rewriteRequired) {
1741  // copy data from original file
1742  switch (m_containerFormat) {
1744  progress.updateStep("Writing MPEG audio frames ...");
1745  break;
1746  default:
1747  progress.updateStep("Writing frames ...");
1748  }
1749  backupStream.seekg(static_cast<streamoff>(streamOffset));
1750  CopyHelper<0x4000> copyHelper;
1751  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1752  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1753  } else {
1754  // just skip actual stream data
1755  outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1756  }
1757 
1758  // write ID3v1 tag
1759  if (m_id3v1Tag) {
1760  progress.updateStep("Writing ID3v1 tag ...");
1761  try {
1762  m_id3v1Tag->make(stream(), diag);
1763  } catch (const Failure &) {
1764  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1765  }
1766  }
1767 
1768  // handle streams
1769  if (rewriteRequired) {
1770  // report new size
1771  reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1772  // "save as path" is now the regular path
1773  if (!saveFilePath().empty()) {
1775  m_saveFilePath.clear();
1776  }
1777  // prevent deferring final write operations (to catch and handle possible errors here); stream is useless for further
1778  // usage anyways because it is write-only
1779  outputStream.close();
1780  } else {
1781  const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1782  if (newSize < size()) {
1783  // file is smaller after the modification -> truncate
1784  // -> prevent deferring final write operations
1785  outputStream.close();
1786  // -> truncate file
1787  if (truncate(BasicFileInfo::pathForOpen(path()).data(), static_cast<streamoff>(newSize)) == 0) {
1788  reportSizeChanged(newSize);
1789  } else {
1790  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1791  }
1792  } else {
1793  // file is longer after the modification
1794  // -> prevent deferring final write operations (to catch and handle possible errors here)
1795  outputStream.flush();
1796  // -> report new size
1797  reportSizeChanged(newSize);
1798  }
1799  }
1800 
1801  } catch (...) {
1802  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1803  }
1804 }
1805 
1806 } // namespace TagParser
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
void parseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the header if not parsed yet.
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:65
const Locale & locale() const
Returns the locale of the track if known; otherwise returns an empty locale.
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
The BasicFileInfo class provides basic file information such as file name, extension,...
Definition: basicfileinfo.h:14
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
const std::string & path() const
Returns the path of the current file.
std::uint64_t size() const
Returns size of the current file in bytes.
virtual void invalidated()
This function is called when the BasicFileInfo gets invalidated.
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:85
void open(bool readOnly=false)
Opens a std::fstream for the current file.
void close()
A possibly opened std::fstream will be closed.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
std::size_t insertFields(const FieldMapBasedTag< ImplementationType > &from, bool overwrite)
Inserts all fields from another tag of the same field type and compare function.
Implementation of TagParser::AbstractTrack for raw FLAC streams.
Definition: flacstream.h:14
VorbisComment * vorbisComment() const
Returns the Vorbis comment if one is present in the stream.
Definition: flacstream.h:51
const std::vector< std::unique_ptr< TrackType > > & tracks() const
Returns the tracks of the file.
Implementation of TagParser::Tag for ID3v1 tags.
Definition: id3v1tag.h:10
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition: id3v1tag.cpp:261
Implementation of TagParser::Tag for ID3v2 tags.
Definition: id3v2tag.h:78
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:74
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
void parseEverything(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the container format, the tracks and the tag information of the current file.
std::uint64_t paddingSize() const
Returns the padding size.
const std::vector< std::unique_ptr< MatroskaTag > > & matroskaTags() const
Returns pointers to the assigned Matroska tags.
std::string_view containerFormatAbbreviation() const
Returns the abbreviation of the container format as C-style string.
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
bool areChaptersSupported() const
Returns an indication whether this library supports parsing the chapters of the current file.
ParsingStatus tagsParsingStatus() const
Returns an indication whether tag information has been parsed yet.
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
VorbisComment * createVorbisComment()
Creates a Vorbis comment for the current file.
VorbisComment * vorbisComment() const
Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
void removeAllTags()
Removes all assigned tags from the file.
~MediaFileInfo() override
Destroys the MediaFileInfo.
bool removeId3v1Tag()
Removes a possibly assigned ID3v1 tag from the current file.
bool hasTracksOfType(TagParser::MediaType type) const
Returns an indication whether the current file has tracks of the specified type.
void invalidated() override
Reimplemented from BasicFileInfo::invalidated().
std::size_t trackCount() const
Returns the number of tracks that could be parsed.
void parseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the attachments of the current file.
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the current file.
std::vector< Tag * > tags() const
Returns all tags assigned to the current file.
bool removeId3v2Tag(Id3v2Tag *tag)
Removes an assigned ID3v2 tag from the current file.
bool areAttachmentsSupported() const
Returns an indication whether this library supports attachment format of the current file.
void parseContainerFormat(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the container format of the current file.
std::size_t maxPadding() const
Returns the maximum padding to be written before the data blocks when applying changes.
ParsingStatus tracksParsingStatus() const
Returns an indication whether tracks have been parsed yet.
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
std::string technicalSummary() const
Generates a short technical summary about the file's tracks.
CppUtilities::TimeSpan duration() const
Returns the overall duration of the file if known; otherwise returns a TimeSpan with zero ticks.
void parseChapters(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the chapters of the current file.
std::size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
bool removeVorbisComment()
Removes all assigned Vorbis comment from the current file.
void parseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tag(s) of the current file.
bool id3v2ToId3v1()
Converts the existing ID3v2 tags into an ID3v1 tag.
Id3v2Tag * createId3v2Tag()
Creates an ID3v2 tag for the current file.
bool areTracksSupported() const
Returns an indication whether this library supports parsing the tracks information of the current fil...
bool createAppropriateTags(const TagCreationSettings &settings=TagCreationSettings())
Ensures appropriate tags are created according the given settings.
void clearParsingResults()
Clears all parsing results and assigned/created/changed information such as detected container format...
std::size_t minPadding() const
Returns the minimum padding to be written before the data blocks when applying changes.
ParsingStatus attachmentsParsingStatus() const
Returns whether the attachments have been parsed yet.
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
bool hasId3v2Tag() const
Returns an indication whether an ID3v2 tag is assigned.
bool isForcingFullParse() const
Returns an indication whether forcing a full parse is enabled.
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).
bool areTagsSupported() const
Returns an indication whether this library supports the tag format of the current file.
bool removeAllId3v2Tags()
Removes all assigned ID3v2 tags from the current file.
AbstractContainer * container() const
Returns the container for the current file.
std::string_view mimeType() const
Returns the MIME-type of the container format as C-style string.
ContainerFormat containerFormat() const
Returns the container format of the current file.
ParsingStatus containerParsingStatus() const
Returns an indication whether the container format has been parsed yet.
bool id3v1ToId3v2()
Converts an existing ID3v1 tag into an ID3v2 tag.
double overallAverageBitrate() const
Returns the overall average bitrate in kbit/s of the file if known; otherwise returns 0....
std::vector< AbstractChapter * > chapters() const
Returns all chapters assigned to the current file.
void applyChanges(Diagnostics &diag, AbortableProgressFeedback &progress)
Applies assigned/changed tag information to the current file.
std::uint64_t containerOffset() const
Returns the actual container start offset.
std::vector< AbstractAttachment * > attachments() const
Returns all attachments assigned to the current file.
void mergeId3v2Tags()
Merges the assigned ID3v2 tags into a single ID3v2 tag.
bool hasId3v1Tag() const
Returns an indication whether an ID3v1 tag is assigned.
ParsingStatus chaptersParsingStatus() const
Returns whether the chapters have been parsed yet.
bool removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
MediaFileInfo()
Constructs a new MediaFileInfo.
bool hasAnyTag() const
Returns an indication whether a tag of any format is assigned.
const std::string & backupDirectory() const
Returns the directory used to store backup files.
Mp4Tag * mp4Tag() const
Returns a pointer to the assigned MP4 tag or nullptr if none is assigned.
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
Definition: mp4container.h:18
Implementation of TagParser::Tag for the MP4 container.
Definition: mp4tag.h:97
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition: exceptions.h:60
Implementation of TagParser::AbstractContainer for OGG files.
Definition: oggcontainer.h:129
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition: exceptions.h:46
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:20
The Tag class is used to store, read and write tag information.
Definition: tag.h:108
virtual std::size_t insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition: tag.cpp:86
Implementation of TagParser::Tag for Vorbis comments.
Definition: vorbiscomment.h:25
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
constexpr TAG_PARSER_EXPORT std::string_view description()
constexpr TAG_PARSER_EXPORT std::string_view language()
constexpr TAG_PARSER_EXPORT std::string_view version()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
ElementPosition
Definition: settings.h:13
@ MpegAudioFrames
Definition: signature.cpp:91
TAG_PARSER_EXPORT std::string_view 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:251
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize)
Definition: signature.h:80
TAG_PARSER_EXPORT std::string_view containerMimeType(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown)
Returns the MIME-type of the container format as C-style string.
Definition: signature.cpp:499
ParsingStatus
The ParsingStatus enum specifies whether a certain part of the file (tracks, tags,...
Definition: mediafileinfo.h:40
MediaType
The MediaType enum specifies the type of media data (audio, video, text, ...).
Definition: mediaformat.h:14
MediaFileStructureFlags
The MediaFileStructureFlags enum specifies flags which describing the structure of a media file.
Definition: mediafileinfo.h:50
ContainerFormat
Specifies the container format.
Definition: signature.h:18
MediaFileHandlingFlags
The MediaFileHandlingFlags enum specifies flags which controls the behavior of MediaFileInfo objects.
Definition: mediafileinfo.h:58
const LocaleDetail & someAbbreviatedName(LocaleFormat preferredFormat=LocaleFormat::BCP_47) const
Returns some abbreviated name, preferrably of the specified preferredFormat.
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
Definition: settings.h:50
std::vector< TagTarget > requiredTargets
Specifies the required targets. If targets are not supported by the container an informal notificatio...
Definition: settings.h:52
std::uint8_t id3v2MajorVersion
Specifies the ID3v2 version to be used in case an ID3v2 tag present or will be created....
Definition: settings.h:61
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:59
TagCreationFlags flags
Specifies options to control the tag creation. See TagSettings::Flags.
Definition: settings.h:54
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:57