Tag Parser  9.4.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 "./language.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 
62 #ifdef FORCE_FULL_PARSE_DEFAULT
63 #define MEDIAINFO_CPP_FORCE_FULL_PARSE true
64 #else
65 #define MEDIAINFO_CPP_FORCE_FULL_PARSE false
66 #endif
67 
81 MediaFileInfo::MediaFileInfo()
82  : m_containerParsingStatus(ParsingStatus::NotParsedYet)
83  , m_containerFormat(ContainerFormat::Unknown)
84  , m_containerOffset(0)
85  , m_actualExistingId3v1Tag(false)
86  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
87  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
88  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
89  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
90  , m_minPadding(0)
91  , m_maxPadding(0)
92  , m_preferredPadding(0)
93  , m_tagPosition(ElementPosition::BeforeData)
94  , m_indexPosition(ElementPosition::BeforeData)
95  , m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
96  , m_forceRewrite(true)
97  , m_forceTagPosition(true)
98  , m_forceIndexPosition(true)
99 {
100 }
101 
107 MediaFileInfo::MediaFileInfo(const string &path)
108  : BasicFileInfo(path)
109  , m_containerParsingStatus(ParsingStatus::NotParsedYet)
110  , m_containerFormat(ContainerFormat::Unknown)
111  , m_containerOffset(0)
112  , m_actualExistingId3v1Tag(false)
113  , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
114  , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
115  , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
116  , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
117  , m_minPadding(0)
118  , m_maxPadding(0)
119  , m_preferredPadding(0)
120  , m_tagPosition(ElementPosition::BeforeData)
121  , m_indexPosition(ElementPosition::BeforeData)
122  , m_forceFullParse(MEDIAINFO_CPP_FORCE_FULL_PARSE)
123  , m_forceRewrite(true)
124  , m_forceTagPosition(true)
125  , m_forceIndexPosition(true)
126 {
127 }
128 
133 {
134 }
135 
152 {
153  // skip if container format already parsed
154  if (containerParsingStatus() != ParsingStatus::NotParsedYet) {
155  return;
156  }
157 
158  static const string context("parsing file header");
159  open(); // ensure the file is open
160  m_containerFormat = ContainerFormat::Unknown;
161 
162  // file size
163  m_paddingSize = 0;
164  m_containerOffset = 0;
165  size_t bytesSkippedBeforeContainer = 0;
166 
167  // read signatrue
168  char buff[16];
169  const char *const buffEnd = buff + sizeof(buff), *buffOffset;
170 startParsingSignature:
171  if (size() - containerOffset() >= 16) {
172  stream().seekg(m_containerOffset, ios_base::beg);
173  stream().read(buff, sizeof(buff));
174 
175  // skip zero/junk bytes
176  // notes:
177  // - Only skipping 4 or more consecutive zero bytes at this point because some signatures start with up to 4 zero bytes.
178  // - It seems that most players/tools¹ skip junk bytes, at least when reading MP3 files. Hence the tagparser library is following
179  // the same approach. (¹e.g. ffmpeg: "[mp3 @ 0x559e1f4cbd80] Skipping 1670 bytes of junk at 1165.")
180  size_t bytesSkipped = 0;
181  for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
182  ;
183  if (bytesSkipped >= 4) {
184  skipJunkBytes:
185  m_containerOffset += bytesSkipped;
186  m_paddingSize += bytesSkipped;
187 
188  // give up after 0x800 bytes
189  if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
190  m_containerParsingStatus = ParsingStatus::NotSupported;
191  m_containerFormat = ContainerFormat::Unknown;
192  return;
193  }
194 
195  // try again
196  goto startParsingSignature;
197  }
198 
199  // parse signature
200  switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
201  case ContainerFormat::Id2v2Tag:
202  // save position of ID3v2 tag
203  m_actualId3v2TagOffsets.push_back(m_containerOffset);
204  if (m_actualId3v2TagOffsets.size() == 2) {
205  diag.emplace_back(DiagLevel::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
206  }
207 
208  // read ID3v2 header
209  stream().seekg(m_containerOffset + 5, ios_base::beg);
210  stream().read(buff, 5);
211 
212  // set the container offset to skip ID3v2 header
213  m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
214  if ((*buff) & 0x10) {
215  // footer present
216  m_containerOffset += 10;
217  }
218 
219  // continue reading signature
220  goto startParsingSignature;
221 
224  // MP4/QuickTime is handled using Mp4Container instance
225  m_container = make_unique<Mp4Container>(*this, m_containerOffset);
226  try {
227  static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
228  } catch (const Failure &) {
229  m_containerParsingStatus = ParsingStatus::CriticalFailure;
230  }
231  break;
232  }
233  case ContainerFormat::Ebml: {
234  // EBML/Matroska is handled using MatroskaContainer instance
235  auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
236  try {
237  container->parseHeader(diag);
238  if (container->documentType() == "matroska") {
239  m_containerFormat = ContainerFormat::Matroska;
240  } else if (container->documentType() == "webm") {
241  m_containerFormat = ContainerFormat::Webm;
242  }
243  if (m_forceFullParse) {
244  // validating the element structure of Matroska files takes too long when
245  // parsing big files so do this only when explicitely desired
246  container->validateElementStructure(diag, &m_paddingSize);
247  container->validateIndex(diag);
248  }
249  } catch (const Failure &) {
250  m_containerParsingStatus = ParsingStatus::CriticalFailure;
251  }
252  m_container = move(container);
253  break;
254  }
256  // Ogg is handled by OggContainer instance
257  m_container = make_unique<OggContainer>(*this, m_containerOffset);
258  static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
259  break;
260  case ContainerFormat::Unknown:
261  // check for magic numbers at odd offsets
262  // -> check for tar (magic number at offset 0x101)
263  if (size() > 0x107) {
264  stream().seekg(0x101);
265  stream().read(buff, 6);
266  if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
267  m_containerFormat = ContainerFormat::Tar;
268  break;
269  }
270  }
271  // skip previously determined zero-bytes or try our luck on the next byte
272  if (!bytesSkipped) {
273  ++bytesSkipped;
274  }
275  goto skipJunkBytes;
276  default:;
277  }
278  }
279 
280  if (bytesSkippedBeforeContainer) {
281  diag.emplace_back(DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer, " bytes of junk skipped"), context);
282  }
283 
284  // set parsing status
285  if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
286  if (m_containerFormat == ContainerFormat::Unknown) {
287  m_containerParsingStatus = ParsingStatus::NotSupported;
288  } else {
289  m_containerParsingStatus = ParsingStatus::Ok;
290  }
291  }
292 }
293 
308 {
309  // skip if tracks already parsed
310  if (tracksParsingStatus() != ParsingStatus::NotParsedYet) {
311  return;
312  }
313  static const string context("parsing tracks");
314 
315  try {
316  // parse tracks via container object
317  if (m_container) {
318  m_container->parseTracks(diag);
319  m_tracksParsingStatus = ParsingStatus::Ok;
320  return;
321  }
322 
323  // parse tracks via track object for "single-track"-formats
324  switch (m_containerFormat) {
326  m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
327  break;
329  m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
330  break;
332  m_singleTrack = make_unique<IvfStream>(stream(), m_containerOffset);
333  break;
335  m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
336  break;
338  m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
339  break;
340  default:
341  throw NotImplementedException();
342  }
343  m_singleTrack->parseHeader(diag);
344 
345  // take padding for some "single-track" formats into account
346  switch (m_containerFormat) {
348  m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
349  break;
350  default:;
351  }
352 
353  m_tracksParsingStatus = ParsingStatus::Ok;
354 
355  } catch (const NotImplementedException &) {
356  diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
357  m_tracksParsingStatus = ParsingStatus::NotSupported;
358  } catch (const Failure &) {
359  diag.emplace_back(DiagLevel::Critical, "Unable to parse tracks.", context);
360  m_tracksParsingStatus = ParsingStatus::CriticalFailure;
361  }
362 }
363 
379 {
380  // skip if tags already parsed
381  if (tagsParsingStatus() != ParsingStatus::NotParsedYet) {
382  return;
383  }
384  static const string context("parsing tag");
385 
386  // check for ID3v1 tag
387  if (size() >= 128) {
388  m_id3v1Tag = make_unique<Id3v1Tag>();
389  try {
390  stream().seekg(-128, ios_base::end);
391  m_id3v1Tag->parse(stream(), diag);
392  m_actualExistingId3v1Tag = true;
393  } catch (const NoDataFoundException &) {
394  m_id3v1Tag.reset();
395  } catch (const Failure &) {
396  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
397  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
398  }
399  }
400 
401  // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
402  m_id3v2Tags.clear();
403  for (const auto offset : m_actualId3v2TagOffsets) {
404  auto id3v2Tag = make_unique<Id3v2Tag>();
405  stream().seekg(offset, ios_base::beg);
406  try {
407  id3v2Tag->parse(stream(), size() - static_cast<std::uint64_t>(offset), diag);
408  m_paddingSize += id3v2Tag->paddingSize();
409  } catch (const NoDataFoundException &) {
410  continue;
411  } catch (const Failure &) {
412  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
413  diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v2 tag.", context);
414  }
415  m_id3v2Tags.emplace_back(id3v2Tag.release());
416  }
417 
418  // check for tags in tracks (FLAC only) or via container object
419  try {
420  if (m_containerFormat == ContainerFormat::Flac) {
421  parseTracks(diag);
422  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
423  m_tagsParsingStatus = m_tracksParsingStatus;
424  }
425  return;
426  } else if (m_container) {
427  m_container->parseTags(diag);
428  } else {
429  throw NotImplementedException();
430  }
431 
432  // set status, but do not override error/unsupported status form ID3 tags here
433  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
434  m_tagsParsingStatus = ParsingStatus::Ok;
435  }
436 
437  } catch (const NotImplementedException &) {
438  // set status to not supported, but do not override parsing status from ID3 tags here
439  if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
440  m_tagsParsingStatus = ParsingStatus::NotSupported;
441  }
442  diag.emplace_back(DiagLevel::Information, "Parsing tags is not implemented for the container format of the file.", context);
443  } catch (const Failure &) {
444  m_tagsParsingStatus = ParsingStatus::CriticalFailure;
445  diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
446  }
447 }
448 
461 {
462  // skip if chapters already parsed
463  if (chaptersParsingStatus() != ParsingStatus::NotParsedYet) {
464  return;
465  }
466  static const string context("parsing chapters");
467 
468  try {
469  // parse chapters via container object
470  if (!m_container) {
471  throw NotImplementedException();
472  }
473  m_container->parseChapters(diag);
474  m_chaptersParsingStatus = ParsingStatus::Ok;
475  } catch (const NotImplementedException &) {
476  m_chaptersParsingStatus = ParsingStatus::NotSupported;
477  diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
478  } catch (const Failure &) {
479  m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
480  diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
481  }
482 }
483 
496 {
497  // skip if attachments already parsed
498  if (attachmentsParsingStatus() != ParsingStatus::NotParsedYet) {
499  return;
500  }
501  static const string context("parsing attachments");
502 
503  try {
504  // parse attachments via container object
505  if (!m_container) {
506  throw NotImplementedException();
507  }
508  m_container->parseAttachments(diag);
509  m_attachmentsParsingStatus = ParsingStatus::Ok;
510  } catch (const NotImplementedException &) {
511  m_attachmentsParsingStatus = ParsingStatus::NotSupported;
512  diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
513  } catch (const Failure &) {
514  m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
515  diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
516  }
517 }
518 
526 {
527  parseContainerFormat(diag);
528  parseTracks(diag);
529  parseTags(diag);
530  parseChapters(diag);
531  parseAttachments(diag);
532 }
533 
546 {
547  // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
548  if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
549  return false;
550  }
551 
552  // check if tags need to be created/adjusted/removed
553  const auto requiredTargets(settings.requiredTargets);
554  const auto flags(settings.flags);
555  const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
556  auto targetsSupported = false;
557  if (areTagsSupported() && m_container) {
558  // container object takes care of tag management
559  if (targetsRequired) {
560  // check whether container supports targets
561  if (m_container->tagCount()) {
562  // all tags in the container should support targets if the first one supports targets
563  targetsSupported = m_container->tag(0)->supportsTarget();
564  } else {
565  // try to create a new tag and check whether targets are supported
566  auto *const tag = m_container->createTag();
567  if (tag && (targetsSupported = tag->supportsTarget())) {
568  tag->setTarget(requiredTargets.front());
569  }
570  }
571  if (targetsSupported) {
572  for (const auto &target : requiredTargets) {
573  m_container->createTag(target);
574  }
575  }
576  } else {
577  // no targets are required -> just ensure that at least one tag is present
578  m_container->createTag();
579  }
580  return true;
581  }
582 
583  // no container object present
584  switch (m_containerFormat) {
586  static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
587  break;
588  default:
589  // create ID3 tag(s)
590  if (!hasAnyTag() && !(flags & TagCreationFlags::TreatUnknownFilesAsMp3Files)) {
591  switch (containerFormat()) {
595  break;
596  default:
597  return false;
598  }
599  }
600  // create ID3 tags according to id3v2usage and id3v2usage
601  // always create ID3v1 tag -> ensure there is one
602  if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
603  auto *const id3v1Tag = createId3v1Tag();
604  if (flags & TagCreationFlags::Id3InitOnCreate) {
605  for (const auto &id3v2Tag : id3v2Tags()) {
606  // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
607  id3v1Tag->insertValues(*id3v2Tag, true);
608  // ID3v1 does not support all text encodings which might be used in ID3v2
610  }
611  }
612  }
613  if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
614  // always create ID3v2 tag -> ensure there is one and set version
615  auto *const id3v2Tag = createId3v2Tag();
616  id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
617  if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
618  id3v2Tag->insertValues(*id3v1Tag(), true);
619  }
620  }
621  }
622 
623  if (flags & TagCreationFlags::MergeMultipleSuccessiveId3v2Tags) {
624  mergeId3v2Tags();
625  }
626  // remove ID3 tags according to settings
627  if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
628  // transfer tags to ID3v2 tag before removing
629  if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) && hasId3v2Tag()) {
630  id3v2Tags().front()->insertValues(*id3v1Tag(), false);
631  }
632  removeId3v1Tag();
633  }
634  if (settings.id3v2usage == TagUsage::Never) {
635  if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) && hasId3v1Tag()) {
636  // transfer tags to ID3v1 tag before removing
637  for (const auto &tag : id3v2Tags()) {
638  id3v1Tag()->insertValues(*tag, false);
639  }
640  }
642  } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
643  // set version of ID3v2 tag according user preferences
644  for (const auto &tag : id3v2Tags()) {
645  tag->setVersion(settings.id3v2MajorVersion, 0);
646  }
647  }
648  return true;
649 }
650 
672 {
673  static const string context("making file");
674  diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
675  bool previousParsingSuccessful = true;
676  switch (tagsParsingStatus()) {
677  case ParsingStatus::Ok:
678  case ParsingStatus::NotSupported:
679  break;
680  default:
681  previousParsingSuccessful = false;
682  diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
683  }
684  switch (tracksParsingStatus()) {
685  case ParsingStatus::Ok:
686  case ParsingStatus::NotSupported:
687  break;
688  default:
689  previousParsingSuccessful = false;
690  diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
691  }
692  if (!previousParsingSuccessful) {
693  throw InvalidDataException();
694  }
695  if (m_container) { // container object takes care
696  // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
697  if (hasId3v1Tag()) {
698  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
699  }
700  if (hasId3v2Tag()) {
701  diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
702  }
703  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
704  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
705  try {
706  m_container->makeFile(diag, progress);
707  } catch (...) {
708  // since the file might be messed up, invalidate the parsing results
710  throw;
711  }
712  } else { // implementation if no container object is present
713  // assume the file is a MP3 file
714  try {
715  makeMp3File(diag, progress);
716  } catch (...) {
717  // since the file might be messed up, invalidate the parsing results
719  throw;
720  }
721  }
723 }
724 
738 {
739  MediaType mediaType = MediaType::Unknown;
740  unsigned int version = 0;
741  switch (m_containerFormat) {
742  case ContainerFormat::Ogg: {
743  // check for video track or whether only Opus or Speex tracks are present
744  const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
745  if (tracks.empty()) {
746  break;
747  }
748  bool onlyOpus = true, onlySpeex = true;
749  for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
750  if (track->mediaType() == MediaType::Video) {
751  mediaType = MediaType::Video;
752  }
753  if (track->format().general != GeneralMediaFormat::Opus) {
754  onlyOpus = false;
755  }
756  if (track->format().general != GeneralMediaFormat::Speex) {
757  onlySpeex = false;
758  }
759  }
760  if (onlyOpus) {
761  version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
762  } else if (onlySpeex) {
763  version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
764  }
765  break;
766  }
767  case ContainerFormat::Matroska:
770  break;
772  if (m_singleTrack) {
773  version = m_singleTrack->format().sub;
774  }
775  break;
776  default:;
777  }
778  return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
779 }
780 
791 const char *MediaFileInfo::mimeType() const
792 {
793  MediaType mediaType;
794  switch (m_containerFormat) {
797  case ContainerFormat::Matroska:
799  break;
800  default:
801  mediaType = MediaType::Unknown;
802  }
803  return TagParser::containerMimeType(m_containerFormat, mediaType);
804 }
805 
818 vector<AbstractTrack *> MediaFileInfo::tracks() const
819 {
820  vector<AbstractTrack *> res;
821  size_t trackCount = 0;
822  size_t containerTrackCount = 0;
823  if (m_singleTrack) {
824  trackCount = 1;
825  }
826  if (m_container) {
827  trackCount += (containerTrackCount = m_container->trackCount());
828  }
829  res.reserve(trackCount);
830 
831  if (m_singleTrack) {
832  res.push_back(m_singleTrack.get());
833  }
834  for (size_t i = 0; i != containerTrackCount; ++i) {
835  res.push_back(m_container->track(i));
836  }
837  return res;
838 }
839 
849 {
850  if (tracksParsingStatus() == ParsingStatus::NotParsedYet) {
851  return false;
852  }
853  if (m_singleTrack && m_singleTrack->mediaType() == type) {
854  return true;
855  } else if (m_container) {
856  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
857  if (m_container->track(i)->mediaType() == type) {
858  return true;
859  }
860  }
861  }
862  return false;
863 }
864 
874 CppUtilities::TimeSpan MediaFileInfo::duration() const
875 {
876  if (m_container) {
877  return m_container->duration();
878  } else if (m_singleTrack) {
879  return m_singleTrack->duration();
880  }
881  return TimeSpan();
882 }
883 
894 {
895  const auto duration = this->duration();
896  if (duration.isNull()) {
897  return 0.0;
898  }
899  return 0.0078125 * size() / duration.totalSeconds();
900 }
901 
912 unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
913 {
914  unordered_set<string> res;
915  if (m_container) {
916  for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
917  const AbstractTrack *track = m_container->track(i);
918  if ((type == MediaType::Unknown || track->mediaType() == type) && isLanguageDefined(track->language())) {
919  res.emplace(track->language());
920  }
921  }
922  } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) && isLanguageDefined(m_singleTrack->language())) {
923  res.emplace(m_singleTrack->language());
924  }
925  return res;
926 }
927 
940 {
941  if (m_container) {
942  const size_t trackCount = m_container->trackCount();
943  vector<string> parts;
944  parts.reserve(trackCount);
945  for (size_t i = 0; i != trackCount; ++i) {
946  const string description(m_container->track(i)->description());
947  if (!description.empty()) {
948  parts.emplace_back(move(description));
949  }
950  }
951  return joinStrings(parts, " / ");
952  } else if (m_singleTrack) {
953  return m_singleTrack->description();
954  }
955  return string();
956 }
957 
968 {
969  if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
970  return false;
971  }
972  if (m_id3v1Tag) {
973  m_id3v1Tag.reset();
974  return true;
975  }
976  return false;
977 }
978 
995 {
996  if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
997  return nullptr;
998  }
999  if (!m_id3v1Tag) {
1000  m_id3v1Tag = make_unique<Id3v1Tag>();
1001  }
1002  return m_id3v1Tag.get();
1003 }
1004 
1016 {
1017  if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
1018  return false;
1019  }
1020  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1021  if (i->get() == tag) {
1022  m_id3v2Tags.erase(i);
1023  return true;
1024  }
1025  }
1026  return false;
1027 }
1028 
1038 {
1039  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1040  return false;
1041  }
1042  m_id3v2Tags.clear();
1043  return true;
1044 }
1045 
1062 {
1063  if (m_id3v2Tags.empty()) {
1064  m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1065  }
1066  return m_id3v2Tags.front().get();
1067 }
1068 
1083 {
1084  if (!tag) {
1085  return false;
1086  }
1087 
1088  // remove tag via container
1089  if (m_container) {
1090  return m_container->removeTag(tag);
1091  }
1092 
1093  // remove tag via track for "single-track" formats
1094  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1095  auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1096  if (flacStream->vorbisComment() == tag) {
1097  return flacStream->removeVorbisComment();
1098  }
1099  }
1100 
1101  // remove ID3 tags
1102  if (m_id3v1Tag.get() == tag) {
1103  m_id3v1Tag.reset();
1104  return true;
1105  }
1106  for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1107  if (i->get() == tag) {
1108  m_id3v2Tags.erase(i);
1109  return true;
1110  }
1111  }
1112  return false;
1113 }
1114 
1122 {
1123  if (m_container) {
1124  m_container->removeAllTags();
1125  }
1126  if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1127  static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1128  }
1129  m_id3v1Tag.reset();
1130  m_id3v2Tags.clear();
1131 }
1132 
1137 {
1138  if (m_container && m_container->chapterCount()) {
1139  return true;
1140  }
1141  switch (m_containerFormat) {
1142  case ContainerFormat::Matroska:
1143  case ContainerFormat::Webm:
1144  return true;
1145  default:
1146  return false;
1147  }
1148 }
1149 
1154 {
1155  if (m_container && m_container->attachmentCount()) {
1156  return true;
1157  }
1158  switch (m_containerFormat) {
1159  case ContainerFormat::Matroska:
1160  case ContainerFormat::Webm:
1161  return true;
1162  default:
1163  return false;
1164  }
1165 }
1166 
1171 {
1172  if (trackCount()) {
1173  return true;
1174  }
1175  switch (m_containerFormat) {
1176  case ContainerFormat::Mp4:
1179  case ContainerFormat::Ogg:
1180  case ContainerFormat::Matroska:
1181  case ContainerFormat::Webm:
1182  return true;
1183  default:
1184  return false;
1185  }
1186 }
1187 
1192 {
1193  switch (m_containerFormat) {
1194  case ContainerFormat::Adts:
1195  case ContainerFormat::Flac:
1196  case ContainerFormat::Matroska:
1198  case ContainerFormat::Mp4:
1199  case ContainerFormat::Ogg:
1201  case ContainerFormat::Webm:
1202  // these container formats are supported
1203  return true;
1204  default:
1205  // the container format is unsupported
1206  // -> an ID3 tag might be already present, in this case the tags are considered supported
1207  return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1208  }
1209 }
1210 
1217 {
1218  // simply return the first tag here since MP4 files never contain multiple tags
1219  return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1220  && m_container->tagCount() > 0
1221  ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1222  : nullptr;
1223 }
1224 
1231 const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1232 {
1233  // matroska files might contain multiple tags (targeting different scopes)
1234  if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1235  return static_cast<MatroskaContainer *>(m_container.get())->tags();
1236  } else {
1237  static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1238  return empty;
1239  }
1240 }
1241 
1248 {
1249  return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1250  ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1251  : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1252 }
1253 
1259 vector<AbstractChapter *> MediaFileInfo::chapters() const
1260 {
1261  vector<AbstractChapter *> res;
1262  if (m_container) {
1263  const size_t count = m_container->chapterCount();
1264  res.reserve(count);
1265  for (size_t i = 0; i != count; ++i) {
1266  res.push_back(m_container->chapter(i));
1267  }
1268  }
1269  return res;
1270 }
1271 
1277 vector<AbstractAttachment *> MediaFileInfo::attachments() const
1278 {
1279  vector<AbstractAttachment *> res;
1280  if (m_container) {
1281  const size_t count = m_container->attachmentCount();
1282  res.reserve(count);
1283  for (size_t i = 0; i != count; ++i) {
1284  res.push_back(m_container->attachment(i));
1285  }
1286  }
1287  return res;
1288 }
1289 
1302 {
1303  m_containerParsingStatus = ParsingStatus::NotParsedYet;
1304  m_containerFormat = ContainerFormat::Unknown;
1305  m_containerOffset = 0;
1306  m_paddingSize = 0;
1307  m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1308  m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1309  m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1310  m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1311  m_id3v1Tag.reset();
1312  m_id3v2Tags.clear();
1313  m_actualId3v2TagOffsets.clear();
1314  m_actualExistingId3v1Tag = false;
1315  m_container.reset();
1316  m_singleTrack.reset();
1317 }
1318 
1336 {
1337  auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1338  if (begin == end) {
1339  return;
1340  }
1341  Id3v2Tag &first = **begin;
1342  auto isecond = begin + 1;
1343  if (isecond == end) {
1344  return;
1345  }
1346  for (auto i = isecond; i != end; ++i) {
1347  first.insertFields(**i, false);
1348  }
1349  m_id3v2Tags.erase(isecond, end - 1);
1350 }
1351 
1363 {
1364  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || !areTagsSupported() || !hasId3v1Tag()) {
1365  return false;
1366  }
1368  {}, TagCreationFlags::MergeMultipleSuccessiveId3v2Tags | TagCreationFlags::KeepExistingId3v2Version, TagUsage::Never, TagUsage::Always, 3 });
1369 }
1370 
1382 {
1383  if (tagsParsingStatus() == ParsingStatus::NotParsedYet || !areTagsSupported() || !hasId3v2Tag()) {
1384  return false;
1385  }
1387  {}, TagCreationFlags::MergeMultipleSuccessiveId3v2Tags | TagCreationFlags::KeepExistingId3v2Version, TagUsage::Always, TagUsage::Never, 3 });
1388 }
1389 
1405 {
1406  switch (m_containerFormat) {
1407  case ContainerFormat::Ogg:
1408  if (m_container) {
1409  return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1410  }
1411  break;
1412  case ContainerFormat::Flac:
1413  if (m_singleTrack) {
1414  return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1415  }
1416  break;
1417  default:;
1418  }
1419  return nullptr;
1420 }
1421 
1432 {
1433  switch (m_containerFormat) {
1434  case ContainerFormat::Ogg:
1435  if (m_container) {
1436  bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1437  static_cast<OggContainer *>(m_container.get())->removeAllTags();
1438  return hadTags;
1439  }
1440  break;
1441  case ContainerFormat::Flac:
1442  if (m_singleTrack) {
1443  return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1444  }
1445  break;
1446  default:;
1447  }
1448  return false;
1449 }
1450 
1459 void MediaFileInfo::tags(vector<Tag *> &tags) const
1460 {
1461  if (hasId3v1Tag()) {
1462  tags.push_back(m_id3v1Tag.get());
1463  }
1464  for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1465  tags.push_back(tag.get());
1466  }
1467  if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1468  if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1469  tags.push_back(vorbisComment);
1470  }
1471  }
1472  if (m_container) {
1473  for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1474  tags.push_back(m_container->tag(i));
1475  }
1476  }
1477 }
1478 
1483 {
1484  return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1485  || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1486 }
1487 
1494 vector<Tag *> MediaFileInfo::tags() const
1495 {
1496  vector<Tag *> res;
1497  tags(res);
1498  return res;
1499 }
1500 
1505 {
1508 }
1509 
1513 void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1514 {
1515  static const string context("making MP3/FLAC file");
1516 
1517  // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1518  if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1519  && m_containerFormat != ContainerFormat::Flac) {
1520  // alter ID3v1 tag
1521  if (!m_id3v1Tag) {
1522  // remove ID3v1 tag
1523  if (!m_actualExistingId3v1Tag) {
1524  diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1525  return;
1526  }
1527  progress.updateStep("Removing ID3v1 tag ...");
1528  stream().close();
1529  if (truncate(BasicFileInfo::pathForOpen(path()), static_cast<std::streamoff>(size() - 128)) == 0) {
1530  reportSizeChanged(size() - 128);
1531  } else {
1532  diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1533  throw std::ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
1534  }
1535  return;
1536  } else {
1537  // add or update ID3v1 tag
1538  if (m_actualExistingId3v1Tag) {
1539  progress.updateStep("Updating existing ID3v1 tag ...");
1540  // ensure the file is still open / not readonly
1541  open();
1542  stream().seekp(-128, ios_base::end);
1543  try {
1544  m_id3v1Tag->make(stream(), diag);
1545  } catch (const Failure &) {
1546  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1547  }
1548  } else {
1549  progress.updateStep("Adding new ID3v1 tag ...");
1550  // ensure the file is still open / not readonly
1551  open();
1552  stream().seekp(0, ios_base::end);
1553  try {
1554  m_id3v1Tag->make(stream(), diag);
1555  } catch (const Failure &) {
1556  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1557  }
1558  }
1559  // prevent deferring final write operations (to catch and handle possible errors here)
1560  stream().flush();
1561  }
1562  return;
1563  }
1564 
1565  // ID3v2 needs to be modified
1566  FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1567  progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1568 
1569  // prepare ID3v2 tags
1570  vector<Id3v2TagMaker> makers;
1571  makers.reserve(m_id3v2Tags.size());
1572  std::uint32_t tagsSize = 0;
1573  for (auto &tag : m_id3v2Tags) {
1574  try {
1575  makers.emplace_back(tag->prepareMaking(diag));
1576  tagsSize += makers.back().requiredSize();
1577  } catch (const Failure &) {
1578  }
1579  }
1580 
1581  // determine stream offset and make track/format specific metadata
1582  std::uint32_t streamOffset; // where the actual stream starts
1583  stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1584  flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1585  std::streamoff startOfLastMetaDataBlock;
1586  if (flacStream) {
1587  // if it is a raw FLAC stream, make FLAC metadata
1588  startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1589  tagsSize += flacMetaData.tellp();
1590  streamOffset = flacStream->streamOffset();
1591  } else {
1592  // make no further metadata, just use the container offset as stream offset
1593  streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1594  }
1595 
1596  // check whether rewrite is required
1597  bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1598  size_t padding = 0;
1599  if (!rewriteRequired) {
1600  // rewriting is not forced and new tag is not too big for available space
1601  // -> calculate new padding
1602  padding = streamOffset - tagsSize;
1603  // -> check whether the new padding matches specifications
1604  if (padding < minPadding() || padding > maxPadding()) {
1605  rewriteRequired = true;
1606  }
1607  }
1608  if (makers.empty() && !flacStream) {
1609  // an ID3v2 tag is not written and it is not a FLAC stream
1610  // -> can't include padding
1611  if (padding) {
1612  // but padding would be present -> need to rewrite
1613  padding = 0; // can't write the preferred padding despite rewriting
1614  rewriteRequired = true;
1615  }
1616  } else if (rewriteRequired) {
1617  // rewriting is forced or new ID3v2 tag is too big for available space
1618  // -> use preferred padding when rewriting anyways
1619  padding = preferredPadding();
1620  } else if (makers.empty() && flacStream && padding && padding < 4) {
1621  // no ID3v2 tag -> must include padding in FLAC stream
1622  // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1623  padding = preferredPadding();
1624  rewriteRequired = true;
1625  }
1626  if (rewriteRequired && flacStream && makers.empty() && padding) {
1627  // the first 4 byte of FLAC padding actually don't count because these
1628  // can not be used for additional meta data
1629  padding += 4;
1630  }
1631  progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1632 
1633  // setup stream(s) for writing
1634  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1635  string backupPath;
1636  NativeFileStream &outputStream = stream();
1637  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1638 
1639  if (rewriteRequired) {
1640  if (m_saveFilePath.empty()) {
1641  // move current file to temp dir and reopen it as backupStream, recreate original file
1642  try {
1643  BackupHelper::createBackupFile(backupDirectory(), path(), backupPath, outputStream, backupStream);
1644  // recreate original file, define buffer variables
1645  outputStream.open(BasicFileInfo::pathForOpen(path()), ios_base::out | ios_base::binary | ios_base::trunc);
1646  } catch (const std::ios_base::failure &failure) {
1647  diag.emplace_back(
1648  DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1649  throw;
1650  }
1651  } else {
1652  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1653  try {
1654  close();
1655  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1656  backupStream.open(BasicFileInfo::pathForOpen(path()), ios_base::in | ios_base::binary);
1657  outputStream.open(BasicFileInfo::pathForOpen(m_saveFilePath), ios_base::out | ios_base::binary | ios_base::trunc);
1658  } catch (const std::ios_base::failure &failure) {
1659  diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1660  throw;
1661  }
1662  }
1663 
1664  } else { // !rewriteRequired
1665  // reopen original file to ensure it is opened for writing
1666  try {
1667  close();
1668  outputStream.open(BasicFileInfo::pathForOpen(path()), ios_base::in | ios_base::out | ios_base::binary);
1669  } catch (const std::ios_base::failure &failure) {
1670  diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1671  throw;
1672  }
1673  }
1674  // TODO: fix code duplication
1675 
1676  // start actual writing
1677  try {
1678  // ensure we can cast padding safely to uint32
1679  if (padding > numeric_limits<std::uint32_t>::max()) {
1680  padding = numeric_limits<std::uint32_t>::max();
1681  diag.emplace_back(
1682  DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1683  }
1684 
1685  if (!makers.empty()) {
1686  // write ID3v2 tags
1687  progress.updateStep("Writing ID3v2 tag ...");
1688  for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1689  i->make(outputStream, 0, diag);
1690  }
1691  // include padding into the last ID3v2 tag
1692  makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1693  }
1694 
1695  if (flacStream) {
1696  if (padding && startOfLastMetaDataBlock) {
1697  // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1698  flacMetaData.seekg(startOfLastMetaDataBlock);
1699  flacMetaData.seekp(startOfLastMetaDataBlock);
1700  flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1701  flacMetaData.seekg(0);
1702  }
1703 
1704  // write FLAC metadata
1705  outputStream << flacMetaData.rdbuf();
1706 
1707  // write padding
1708  if (padding) {
1709  flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding), true, diag);
1710  }
1711  }
1712 
1713  if (makers.empty() && !flacStream) {
1714  // just write padding (however, padding should be set to 0 in this case?)
1715  for (; padding; --padding) {
1716  outputStream.put(0);
1717  }
1718  }
1719 
1720  // copy / skip actual stream data
1721  // -> determine media data size
1722  std::uint64_t mediaDataSize = size() - streamOffset;
1723  if (m_actualExistingId3v1Tag) {
1724  mediaDataSize -= 128;
1725  }
1726 
1727  if (rewriteRequired) {
1728  // copy data from original file
1729  switch (m_containerFormat) {
1731  progress.updateStep("Writing MPEG audio frames ...");
1732  break;
1733  default:
1734  progress.updateStep("Writing frames ...");
1735  }
1736  backupStream.seekg(static_cast<streamoff>(streamOffset));
1737  CopyHelper<0x4000> copyHelper;
1738  copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1739  bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1740  } else {
1741  // just skip actual stream data
1742  outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1743  }
1744 
1745  // write ID3v1 tag
1746  if (m_id3v1Tag) {
1747  progress.updateStep("Writing ID3v1 tag ...");
1748  try {
1749  m_id3v1Tag->make(stream(), diag);
1750  } catch (const Failure &) {
1751  diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1752  }
1753  }
1754 
1755  // handle streams
1756  if (rewriteRequired) {
1757  // report new size
1758  reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1759  // "save as path" is now the regular path
1760  if (!saveFilePath().empty()) {
1762  m_saveFilePath.clear();
1763  }
1764  // prevent deferring final write operations (to catch and handle possible errors here); stream is useless for further
1765  // usage anyways because it is write-only
1766  outputStream.close();
1767  } else {
1768  const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1769  if (newSize < size()) {
1770  // file is smaller after the modification -> truncate
1771  // -> prevent deferring final write operations
1772  outputStream.close();
1773  // -> truncate file
1774  if (truncate(BasicFileInfo::pathForOpen(path()), static_cast<streamoff>(newSize)) == 0) {
1775  reportSizeChanged(newSize);
1776  } else {
1777  diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1778  }
1779  } else {
1780  // file is longer after the modification
1781  // -> prevent deferring final write operations (to catch and handle possible errors here)
1782  outputStream.flush();
1783  // -> report new size
1784  reportSizeChanged(newSize);
1785  }
1786  }
1787 
1788  } catch (...) {
1789  BackupHelper::handleFailureAfterFileModified(*this, backupPath, outputStream, backupStream, diag, context);
1790  }
1791 }
1792 
1793 } // namespace TagParser
TagParser::Mp4Container
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
Definition: mp4container.h:18
mp4ids.h
TagParser::GenericContainer::tracks
const std::vector< std::unique_ptr< TrackType > > & tracks() const
Returns the tracks of the file.
Definition: genericcontainer.h:237
TagParser::MediaFileInfo::mergeId3v2Tags
void mergeId3v2Tags()
Merges the assigned ID3v2 tags into a single ID3v2 tag.
Definition: mediafileinfo.cpp:1335
mp4atom.h
exceptions.h
TagParser::VorbisCommentIds::version
constexpr TAG_PARSER_EXPORT const char * version()
Definition: vorbiscommentids.h:34
TagParser::MatroskaTagIds::description
constexpr TAG_PARSER_EXPORT const char * description()
Definition: matroskatagid.h:231
TagParser::MediaFileInfo::MediaFileInfo
MediaFileInfo()
Constructs a new MediaFileInfo.
Definition: mediafileinfo.cpp:81
TagParser::TagCreationSettings
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
Definition: settings.h:50
TagParser::MediaFileInfo::removeTag
bool removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
Definition: mediafileinfo.cpp:1082
TagParser::BasicFileInfo::invalidated
virtual void invalidated()
This function is called when the BasicFileInfo gets invalidated.
Definition: basicfileinfo.cpp:218
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition: progressfeedback.h:186
TagParser::MediaFileInfo::mp4Tag
Mp4Tag * mp4Tag() const
Returns a pointer to the assigned MP4 tag or nullptr if none is assigned.
Definition: mediafileinfo.cpp:1216
TagParser::parseSignature
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
Definition: signature.cpp:104
TagParser::Id3v1Tag::ensureTextValuesAreProperlyEncoded
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition: id3v1tag.cpp:265
TagParser::MediaFileInfo::parseTags
void parseTags(Diagnostics &diag)
Parses the tag(s) of the current file.
Definition: mediafileinfo.cpp:378
adtsstream.h
TagParser::MediaFileInfo::tracks
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
Definition: mediafileinfo.cpp:818
TagParser::MediaFileInfo::parseContainerFormat
void parseContainerFormat(Diagnostics &diag)
Parses the container format of the current file.
Definition: mediafileinfo.cpp:151
waveaudiostream.h
TagParser::MediaFileInfo::matroskaTags
const std::vector< std::unique_ptr< MatroskaTag > > & matroskaTags() const
Returns pointers to the assigned Matroska tags.
Definition: mediafileinfo.cpp:1231
TagParser::MediaFileInfo::saveFilePath
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
Definition: mediafileinfo.h:370
TagParser::MediaFileInfo::removeId3v2Tag
bool removeId3v2Tag(Id3v2Tag *tag)
Removes an assigned ID3v2 tag from the current file.
Definition: mediafileinfo.cpp:1015
TagParser::MediaFileInfo::invalidated
void invalidated() override
Reimplemented from BasicFileInfo::invalidated().
Definition: mediafileinfo.cpp:1504
TagParser::MediaFileInfo::maxPadding
std::size_t maxPadding() const
Returns the maximum padding to be written before the data blocks when applying changes.
Definition: mediafileinfo.h:516
backuphelper.h
mp4track.h
TagParser::MediaFileInfo::removeId3v1Tag
bool removeId3v1Tag()
Removes a possibly assigned ID3v1 tag from the current file.
Definition: mediafileinfo.cpp:967
TagParser::AbortableProgressFeedback::isAborted
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
Definition: progressfeedback.h:226
TagParser::FourccIds::WavPack
@ WavPack
Definition: mp4ids.h:390
TagParser::Ogg
@ Ogg
Definition: signature.cpp:54
TagParser::MediaFileInfo::attachments
std::vector< AbstractAttachment * > attachments() const
Returns all attachments assigned to the current file.
Definition: mediafileinfo.cpp:1277
TagParser::MediaFileInfo::id3v1ToId3v2
bool id3v1ToId3v2()
Converts an existing ID3v1 tag into an ID3v2 tag.
Definition: mediafileinfo.cpp:1362
TagParser::OggContainer
Implementation of TagParser::AbstractContainer for OGG files.
Definition: oggcontainer.h:129
TagParser::BasicFileInfo::pathForOpen
static const char * pathForOpen(const std::string &url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
Definition: basicfileinfo.h:140
TagParser::MediaFileInfo::parseTracks
void parseTracks(Diagnostics &diag)
Parses the tracks of the current file.
Definition: mediafileinfo.cpp:307
progressfeedback.h
TagParser::MediaFileInfo::parseChapters
void parseChapters(Diagnostics &diag)
Parses the chapters of the current file.
Definition: mediafileinfo.cpp:460
TagParser::Ebml
@ Ebml
Definition: signature.cpp:50
TagParser::MediaFileInfo::duration
CppUtilities::TimeSpan duration() const
Returns the overall duration of the file if known; otherwise returns a TimeSpan with zero ticks.
Definition: mediafileinfo.cpp:874
TagParser::MediaFileInfo::availableLanguages
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).
Definition: mediafileinfo.cpp:912
TagParser::Tag
The Tag class is used to store, read and write tag information.
Definition: tag.h:108
TagParser::MediaFileInfo::id3v1Tag
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
Definition: mediafileinfo.h:330
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::MediaFileInfo::createId3v2Tag
Id3v2Tag * createId3v2Tag()
Creates an ID3v2 tag for the current file.
Definition: mediafileinfo.cpp:1061
TagParser::MediaFileInfo::chaptersParsingStatus
ParsingStatus chaptersParsingStatus() const
Returns whether the chapters have been parsed yet.
Definition: mediafileinfo.h:294
TagParser::MediaFileInfo::chapters
std::vector< AbstractChapter * > chapters() const
Returns all chapters assigned to the current file.
Definition: mediafileinfo.cpp:1259
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::MediaFileInfo::areTagsSupported
bool areTagsSupported() const
Returns an indication whether this library supports the tag format of the current file.
Definition: mediafileinfo.cpp:1191
TagParser::TagCreationSettings::requiredTargets
std::vector< TagTarget > requiredTargets
Specifies the required targets. If targets are not supported by the container an informal notificatio...
Definition: settings.h:52
matroskacontainer.h
TagParser::FlacStream::vorbisComment
VorbisComment * vorbisComment() const
Returns the Vorbis comment if one is present in the stream.
Definition: flacstream.h:51
TagParser::FourccIds::Flac
@ Flac
Definition: mp4ids.h:272
TagParser::FourccIds::Opus
@ Opus
Definition: mp4ids.h:346
TagParser::BasicFileInfo::open
void open(bool readOnly=false)
Opens a std::fstream for the current file.
Definition: basicfileinfo.cpp:46
TagParser::BackupHelper::handleFailureAfterFileModified
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")
TagParser::isLanguageDefined
bool isLanguageDefined(const std::string &languageSpecification)
Returns whether languageSpecification is not empty or undefined.
Definition: language.h:16
TagParser::MediaFileInfo::removeVorbisComment
bool removeVorbisComment()
Removes all assigned Vorbis comment from the current file.
Definition: mediafileinfo.cpp:1431
TagParser::MediaFileInfo::parseAttachments
void parseAttachments(Diagnostics &diag)
Parses the attachments of the current file.
Definition: mediafileinfo.cpp:495
matroskatag.h
TagParser::containerFormatAbbreviation
TAG_PARSER_EXPORT const char * containerFormatAbbreviation(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown, unsigned int version=0)
Returns the abbreviation of the container format as C-style string considering the specified media ty...
Definition: signature.cpp:251
TagParser::MatroskaElementLevel::TopLevel
@ TopLevel
TagParser::MediaFileInfo::minPadding
std::size_t minPadding() const
Returns the minimum padding to be written before the data blocks when applying changes.
Definition: mediafileinfo.h:488
TagParser::AbstractContainer::documentType
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string.
Definition: abstractcontainer.h:227
id3v1tag.h
ivfstream.h
TagParser::MediaFileInfo::createAppropriateTags
bool createAppropriateTags(const TagCreationSettings &settings=TagCreationSettings())
Ensures appropriate tags are created according the given settings.
Definition: mediafileinfo.cpp:545
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
matroskatrack.h
signature.h
TagParser::BackupHelper::createBackupFile
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TagParser::MediaFileInfo::removeAllId3v2Tags
bool removeAllId3v2Tags()
Removes all assigned ID3v2 tags from the current file.
Definition: mediafileinfo.cpp:1037
TagParser::MediaType
MediaType
The MediaType enum specifies the type of media data (audio, video, text, ...).
Definition: mediaformat.h:13
TagParser::QuickTime
@ QuickTime
Definition: signature.cpp:56
TagParser::AbstractTrack::mediaType
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
Definition: abstracttrack.h:300
TagParser::BasicFileInfo::reportPathChanged
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
Definition: basicfileinfo.h:129
language.h
TagParser::MediaFileInfo::container
AbstractContainer * container() const
Returns the container for the current file.
Definition: mediafileinfo.h:432
TagParser::FlacStream
Implementation of TagParser::AbstractTrack for raw FLAC streams.
Definition: flacstream.h:14
TagParser::BasicFileInfo
The BasicFileInfo class provides basic file information such as file name, extension,...
Definition: basicfileinfo.h:14
TagParser::MediaFileInfo::createVorbisComment
VorbisComment * createVorbisComment()
Creates a Vorbis comment for the current file.
Definition: mediafileinfo.cpp:1404
CppUtilities
Definition: abstractcontainer.h:15
diagnostics.h
TagParser::MediaFileInfo::containerParsingStatus
ParsingStatus containerParsingStatus() const
Returns an indication whether the container format has been parsed yet.
Definition: mediafileinfo.h:200
TagParser::MediaFileInfo::trackCount
std::size_t trackCount() const
Returns the number of tracks that could be parsed.
Definition: mediafileinfo.h:286
TagParser::BasicProgressFeedback::updateStep
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
Definition: progressfeedback.h:96
TagParser::Tag::insertValues
virtual unsigned int insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition: tag.cpp:86
TagParser::MediaFileInfo::hasId3v2Tag
bool hasId3v2Tag() const
Returns an indication whether an ID3v2 tag is assigned.
Definition: mediafileinfo.h:318
TagParser::Ivf
@ Ivf
Definition: signature.cpp:48
TagParser::MediaFileInfo::tagsParsingStatus
ParsingStatus tagsParsingStatus() const
Returns an indication whether tag information has been parsed yet.
Definition: mediafileinfo.h:265
TagParser::MediaFileInfo::hasId3v1Tag
bool hasId3v1Tag() const
Returns an indication whether an ID3v1 tag is assigned.
Definition: mediafileinfo.h:310
TagParser::MediaFileInfo::overallAverageBitrate
double overallAverageBitrate() const
Returns the overall average bitrate in kbit/s of the file if known; otherwise returns 0....
Definition: mediafileinfo.cpp:893
TagParser::AbstractTrack
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:39
flacstream.h
TagParser::MediaFileInfo::clearParsingResults
void clearParsingResults()
Clears all parsing results and assigned/created/changed information such as detected container format...
Definition: mediafileinfo.cpp:1301
TagParser::ParsingStatus
ParsingStatus
The ParsingStatus enum specifies whether a certain part of the file (tracks, tags,...
Definition: mediafileinfo.h:38
TagParser::Id3v2Tag
Implementation of TagParser::Tag for ID3v2 tags.
Definition: id3v2tag.h:61
TagParser::MediaFileInfo::tags
std::vector< Tag * > tags() const
Returns all tags assigned to the current file.
Definition: mediafileinfo.cpp:1494
TagParser::ContainerFormat
ContainerFormat
Specifies the container format.
Definition: signature.h:17
TagParser::containerMimeType
TAG_PARSER_EXPORT const char * containerMimeType(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown)
Returns the MIME-type of the container format as C-style string.
Definition: signature.cpp:499
TagParser::TagCreationSettings::id3v1usage
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
id3v2tag.h
TagParser::Id3v1Tag
Implementation of TagParser::Tag for ID3v1 tags.
Definition: id3v1tag.h:10
CppUtilities::CopyHelper
Definition: oggcontainer.h:16
TagParser::MediaFileInfo::parseEverything
void parseEverything(Diagnostics &diag)
Parses the container format, the tracks and the tag information of the current file.
Definition: mediafileinfo.cpp:525
TagParser::MediaFileInfo::id3v2Tags
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
Definition: mediafileinfo.h:342
TagParser::Adts
@ Adts
Definition: signature.cpp:86
TagParser::MpegAudioFrames
@ MpegAudioFrames
Definition: signature.cpp:91
TagParser::MediaFileInfo::removeAllTags
void removeAllTags()
Removes all assigned tags from the file.
Definition: mediafileinfo.cpp:1121
TagParser::MediaFileInfo::technicalSummary
std::string technicalSummary() const
Generates a short technical summary about the file's tracks.
Definition: mediafileinfo.cpp:939
TagParser::ParsingStatus::NotParsedYet
@ NotParsedYet
TagParser::BasicProgressFeedback< AbortableProgressFeedback >::updateStepPercentage
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
Definition: progressfeedback.h:124
TagParser::MediaFileInfo::id3v2ToId3v1
bool id3v2ToId3v1()
Converts the existing ID3v2 tags into an ID3v1 tag.
Definition: mediafileinfo.cpp:1381
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
TagParser::BasicFileInfo::reportSizeChanged
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
Definition: basicfileinfo.h:120
TagParser::TagTarget
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:20
TagParser::ElementPosition
ElementPosition
Definition: settings.h:13
TagParser::Mp4Tag
Implementation of TagParser::Tag for the MP4 container.
Definition: mp4tag.h:97
abstracttrack.h
TagParser::MediaFileInfo::mimeType
const char * mimeType() const
Returns the MIME-type of the container format as C-style string.
Definition: mediafileinfo.cpp:791
TagParser::FieldMapBasedTag::insertFields
int insertFields(const FieldMapBasedTag< ImplementationType > &from, bool overwrite)
Inserts all fields from another tag of the same field type and compare function.
Definition: fieldbasedtag.h:366
TagParser::MediaFileInfo::~MediaFileInfo
~MediaFileInfo() override
Destroys the MediaFileInfo.
Definition: mediafileinfo.cpp:132
TagParser::AbstractTrack::language
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
Definition: abstracttrack.h:417
TagParser::MediaFileInfo::containerOffset
std::uint64_t containerOffset() const
Returns the actual container start offset.
Definition: mediafileinfo.h:249
TagParser::BasicFileInfo::size
std::uint64_t size() const
Returns size of the current file in bytes.
Definition: basicfileinfo.h:111
TagParser::MediaFileInfo::areChaptersSupported
bool areChaptersSupported() const
Returns an indication whether this library supports parsing the chapters of the current file.
Definition: mediafileinfo.cpp:1136
TagParser::MediaFileInfo::attachmentsParsingStatus
ParsingStatus attachmentsParsingStatus() const
Returns whether the attachments have been parsed yet.
Definition: mediafileinfo.h:302
TagParser::RiffWave
@ RiffWave
Definition: signature.cpp:58
flacmetadata.h
TagParser::MatroskaTrackType::Video
@ Video
Definition: matroskaid.h:405
tag.h
ebmlelement.h
TagParser::MediaFileInfo::createId3v1Tag
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
Definition: mediafileinfo.cpp:994
TagParser::TagCreationSettings::id3v2usage
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
TagParser::TagCreationFlags::None
@ None
TagParser::MediaFileInfo::paddingSize
std::uint64_t paddingSize() const
Returns the padding size.
Definition: mediafileinfo.h:257
TagParser::BasicFileInfo::stream
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:81
TagParser::MediaFileInfo::tracksParsingStatus
ParsingStatus tracksParsingStatus() const
Returns an indication whether tracks have been parsed yet.
Definition: mediafileinfo.h:273
TagParser::MediaFileInfo::areAttachmentsSupported
bool areAttachmentsSupported() const
Returns an indication whether this library supports attachment format of the current file.
Definition: mediafileinfo.cpp:1153
TagParser::MatroskaContainer
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
Definition: matroskacontainer.h:24
TagParser::BasicFileInfo::path
const std::string & path() const
Returns the path of the current file.
Definition: basicfileinfo.h:99
TagParser::MediaFileInfo::preferredPadding
std::size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
Definition: mediafileinfo.h:537
TagParser::MediaFileInfo::containerFormatAbbreviation
const char * containerFormatAbbreviation() const
Returns the abbreviation of the container format as C-style string.
Definition: mediafileinfo.cpp:737
TagParser::TagCreationSettings::flags
TagCreationFlags flags
Specifies options to control the tag creation. See TagSettings::Flags.
Definition: settings.h:54
TagParser::BasicFileInfo::close
void close()
A possibly opened std::fstream will be closed.
Definition: basicfileinfo.cpp:71
mp4container.h
MEDIAINFO_CPP_FORCE_FULL_PARSE
#define MEDIAINFO_CPP_FORCE_FULL_PARSE
Definition: mediafileinfo.cpp:65
TagParser::MediaFileInfo::applyChanges
void applyChanges(Diagnostics &diag, AbortableProgressFeedback &progress)
Applies assigned/changed tag information to the current file.
Definition: mediafileinfo.cpp:671
TagParser::VorbisComment
Implementation of TagParser::Tag for Vorbis comments.
Definition: vorbiscomment.h:25
TagParser::MediaFileInfo::areTracksSupported
bool areTracksSupported() const
Returns an indication whether this library supports parsing the tracks information of the current fil...
Definition: mediafileinfo.cpp:1170
TagParser::MediaFileInfo::hasTracksOfType
bool hasTracksOfType(TagParser::MediaType type) const
Returns an indication whether the current file has tracks of the specified type.
Definition: mediafileinfo.cpp:848
TagParser::TagCreationSettings::id3v2MajorVersion
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
TagParser::MediaFileInfo::backupDirectory
const std::string & backupDirectory() const
Returns the directory used to store backup files.
Definition: mediafileinfo.h:352
mediafileinfo.h
TagParser::MediaFileInfo::containerFormat
ContainerFormat containerFormat() const
Returns the container format of the current file.
Definition: mediafileinfo.h:211
oggcontainer.h
TagParser::TagUsage::Always
@ Always
TagParser::MediaFileInfo::isForcingRewrite
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
Definition: mediafileinfo.h:463
TagParser::MediaFileInfo::vorbisComment
VorbisComment * vorbisComment() const
Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
Definition: mediafileinfo.cpp:1247
mpegaudioframestream.h
TagParser::MatroskaTrackType::Audio
@ Audio
Definition: matroskaid.h:405
TagParser::NotImplementedException
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition: exceptions.h:60
TagParser::Mp4
@ Mp4
Definition: signature.cpp:53
TagParser::AbstractContainer::parseHeader
void parseHeader(Diagnostics &diag)
Parses the header if not parsed yet.
Definition: abstractcontainer.cpp:55
TagParser::MediaFileInfo::hasAnyTag
bool hasAnyTag() const
Returns an indication whether a tag of any format is assigned.
Definition: mediafileinfo.cpp:1482
mp4tag.h