Tag Parser  10.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1 #include "./mediafileinfo.h"
2 #include "./abstracttrack.h"
3 #include "./backuphelper.h"
4 #include "./diagnostics.h"
5 #include "./exceptions.h"
6 #include "./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 explicitly 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 
1473 void MediaFileInfo::tags(std::vector<Tag *> &tags) const
1474 {
1475  if (hasId3v1Tag()) {
1476  tags.push_back(m_id3v1Tag.get());
1477  }
1478  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1479  tags.push_back(tag.get());
1480  }
1481  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1482  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1483  tags.push_back(vorbisComment);
1484  }
1485  }
1486  if (m_container) {
1487  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1488  tags.push_back(m_container->tag(i));
1489  }
1490  }
1491 }
1492 
1501 vector<Tag *> MediaFileInfo::tags() const
1502 {
1503  auto res = vector<Tag *>();
1504  tags(res);
1505  return res;
1506 }
1507 
1515 {
1516  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1517  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1518 }
1519 
1529 void MediaFileInfo::parsedTags(std::vector<Tag *> &tags) const
1530 {
1531  if (hasId3v1Tag() && m_id3v1Tag->size()) {
1532  tags.push_back(m_id3v1Tag.get());
1533  }
1534  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1535  if (tag->size()) {
1536  tags.push_back(tag.get());
1537  }
1538  }
1539  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1540  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1541  if (vorbisComment->size()) {
1542  tags.push_back(vorbisComment);
1543  }
1544  }
1545  }
1546  if (m_container) {
1547  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1548  if (auto *const tag = m_container->tag(i); tag->size()) {
1549  tags.push_back(tag);
1550  }
1551  }
1552  }
1553 }
1554 
1563 std::vector<Tag *> MediaFileInfo::parsedTags() const
1564 {
1565  auto res = vector<Tag *>();
1566  parsedTags(res);
1567  return res;
1568 }
1569 
1574 {
1577 }
1578 
1582 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1583 {
1584  static const string context("making MP3/FLAC file");
1585 
1586  // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1587  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1588  && m_containerFormat != ContainerFormat::Flac) {
1589  // alter ID3v1 tag
1590  if (!m_id3v1Tag) {
1591  // remove ID3v1 tag
1592  if (!(m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag)) {
1593  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1594  return;
1595  }
1596  progress.updateStep("Removing ID3v1 tag ...");
1597  stream().close();
1598  if (truncate(BasicFileInfo::pathForOpen(path()).data(), static_cast<std::streamoff>(size() - 128)) == 0) {
1599  reportSizeChanged(size() - 128);
1600  } else {
1601  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1602  throw std::ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
1603  }
1604  return;
1605  } else {
1606  // add or update ID3v1 tag
1607  if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1608  progress.updateStep("Updating existing ID3v1 tag ...");
1609  // ensure the file is still open / not readonly
1610  open();
1611  stream().seekp(-128, ios_base::end);
1612  try {
1613  m_id3v1Tag->make(stream(), diag);
1614  } catch (const Failure &) {
1615  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1616  }
1617  } else {
1618  progress.updateStep("Adding new ID3v1 tag ...");
1619  // ensure the file is still open / not readonly
1620  open();
1621  stream().seekp(0, ios_base::end);
1622  try {
1623  m_id3v1Tag->make(stream(), diag);
1624  } catch (const Failure &) {
1625  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1626  }
1627  }
1628  // prevent deferring final write operations (to catch and handle possible errors here)
1629  stream().flush();
1630  }
1631  return;
1632  }
1633 
1634  // ID3v2 needs to be modified
1635  FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1636  progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1637 
1638  // prepare ID3v2 tags
1639  vector<Id3v2TagMaker> makers;
1640  makers.reserve(m_id3v2Tags.size());
1641  std::uint64_t tagsSize = 0;
1642  for (auto &tag : m_id3v2Tags) {
1643  try {
1644  makers.emplace_back(tag->prepareMaking(diag));
1645  tagsSize += makers.back().requiredSize();
1646  } catch (const Failure &) {
1647  }
1648  }
1649 
1650  // determine stream offset and make track/format specific metadata
1651  std::uint32_t streamOffset; // where the actual stream starts
1652  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1653  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1654  std::streamoff startOfLastMetaDataBlock;
1655  if (flacStream) {
1656  // if it is a raw FLAC stream, make FLAC metadata
1657  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1658  tagsSize += static_cast<std::uint64_t>(flacMetaData.tellp());
1659  streamOffset = flacStream->streamOffset();
1660  } else {
1661  // make no further metadata, just use the container offset as stream offset
1662  streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1663  }
1664 
1665  // check whether rewrite is required
1666  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1667  size_t padding = 0;
1668  if (!rewriteRequired) {
1669  // rewriting is not forced and new tag is not too big for available space
1670  // -> calculate new padding
1671  padding = streamOffset - tagsSize;
1672  // -> check whether the new padding matches specifications
1673  if (padding < minPadding() || padding > maxPadding()) {
1674  rewriteRequired = true;
1675  }
1676  }
1677  if (makers.empty() && !flacStream) {
1678  // an ID3v2 tag is not written and it is not a FLAC stream
1679  // -> can't include padding
1680  if (padding) {
1681  // but padding would be present -> need to rewrite
1682  padding = 0; // can't write the preferred padding despite rewriting
1683  rewriteRequired = true;
1684  }
1685  } else if (rewriteRequired) {
1686  // rewriting is forced or new ID3v2 tag is too big for available space
1687  // -> use preferred padding when rewriting anyways
1688  padding = preferredPadding();
1689  } else if (makers.empty() && flacStream && padding && padding < 4) {
1690  // no ID3v2 tag -> must include padding in FLAC stream
1691  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1692  padding = preferredPadding();
1693  rewriteRequired = true;
1694  }
1695  if (rewriteRequired && flacStream && makers.empty() && padding) {
1696  // the first 4 byte of FLAC padding actually don't count because these
1697  // can not be used for additional meta data
1698  padding += 4;
1699  }
1700  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1701 
1702  // setup stream(s) for writing
1703  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1704  string backupPath;
1705  NativeFileStream &outputStream = stream();
1706  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1707 
1708  if (rewriteRequired) {
1709  if (m_saveFilePath.empty()) {
1710  // move current file to temp dir and reopen it as backupStream, recreate original file
1711  try {
1712  BackupHelper::createBackupFile(backupDirectory(), path(), backupPath, outputStream, backupStream);
1713  // recreate original file, define buffer variables
1714  outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1715  } catch (const std::ios_base::failure &failure) {
1716  diag.emplace_back(
1717  DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1718  throw;
1719  }
1720  } else {
1721  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1722  try {
1723  close();
1724  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1725  backupStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::binary);
1726  outputStream.open(BasicFileInfo::pathForOpen(m_saveFilePath).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1727  } catch (const std::ios_base::failure &failure) {
1728  diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1729  throw;
1730  }
1731  }
1732 
1733  } else { // !rewriteRequired
1734  // reopen original file to ensure it is opened for writing
1735  try {
1736  close();
1737  outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::out | ios_base::binary);
1738  } catch (const std::ios_base::failure &failure) {
1739  diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1740  throw;
1741  }
1742  }
1743  // TODO: fix code duplication
1744 
1745  // start actual writing
1746  try {
1747  // ensure we can cast padding safely to uint32
1748  if (padding > numeric_limits<std::uint32_t>::max()) {
1749  padding = numeric_limits<std::uint32_t>::max();
1750  diag.emplace_back(
1751  DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1752  }
1753 
1754  if (!makers.empty()) {
1755  // write ID3v2 tags
1756  progress.updateStep("Writing ID3v2 tag ...");
1757  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1758  i->make(outputStream, 0, diag);
1759  }
1760  // include padding into the last ID3v2 tag
1761  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1762  }
1763 
1764  if (flacStream) {
1765  if (padding && startOfLastMetaDataBlock) {
1766  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1767  flacMetaData.seekg(startOfLastMetaDataBlock);
1768  flacMetaData.seekp(startOfLastMetaDataBlock);
1769  flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1770  flacMetaData.seekg(0);
1771  }
1772 
1773  // write FLAC metadata
1774  outputStream << flacMetaData.rdbuf();
1775 
1776  // write padding
1777  if (padding) {
1778  flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding), true, diag);
1779  }
1780  }
1781 
1782  if (makers.empty() && !flacStream) {
1783  // just write padding (however, padding should be set to 0 in this case?)
1784  for (; padding; --padding) {
1785  outputStream.put(0);
1786  }
1787  }
1788 
1789  // copy / skip actual stream data
1790  // -> determine media data size
1791  std::uint64_t mediaDataSize = size() - streamOffset;
1792  if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1793  mediaDataSize -= 128;
1794  }
1795 
1796  if (rewriteRequired) {
1797  // copy data from original file
1798  switch (m_containerFormat) {
1800  progress.updateStep("Writing MPEG audio frames ...");
1801  break;
1802  default:
1803  progress.updateStep("Writing frames ...");
1804  }
1805  backupStream.seekg(static_cast<streamoff>(streamOffset));
1806  CopyHelper<0x4000> copyHelper;
1807  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1808  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1809  } else {
1810  // just skip actual stream data
1811  outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1812  }
1813 
1814  // write ID3v1 tag
1815  if (m_id3v1Tag) {
1816  progress.updateStep("Writing ID3v1 tag ...");
1817  try {
1818  m_id3v1Tag->make(stream(), diag);
1819  } catch (const Failure &) {
1820  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1821  }
1822  }
1823 
1824  // handle streams
1825  if (rewriteRequired) {
1826  // report new size
1827  reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1828  // "save as path" is now the regular path
1829  if (!saveFilePath().empty()) {
1831  m_saveFilePath.clear();
1832  }
1833  // prevent deferring final write operations (to catch and handle possible errors here); stream is useless for further
1834  // usage anyways because it is write-only
1835  outputStream.close();
1836  } else {
1837  const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1838  if (newSize < size()) {
1839  // file is smaller after the modification -> truncate
1840  // -> prevent deferring final write operations
1841  outputStream.close();
1842  // -> truncate file
1843  if (truncate(BasicFileInfo::pathForOpen(path()).data(), static_cast<streamoff>(newSize)) == 0) {
1844  reportSizeChanged(newSize);
1845  } else {
1846  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1847  }
1848  } else {
1849  // file is longer after the modification
1850  // -> prevent deferring final write operations (to catch and handle possible errors here)
1851  outputStream.flush();
1852  // -> report new size
1853  reportSizeChanged(newSize);
1854  }
1855  }
1856 
1857  } catch (...) {
1858  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1859  }
1860 }
1861 
1862 } // 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.
std::vector< Tag * > parsedTags() const
Returns all tags parsed from 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
std::uint64_t size() const
Returns the size the tag within the file it is parsed from in bytes.
Definition: tag.h:174
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, preferably 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