Tag Parser 10.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mediafileinfo.cpp
Go to the documentation of this file.
1#include "./mediafileinfo.h"
2#include "./abstracttrack.h"
3#include "./backuphelper.h"
4#include "./diagnostics.h"
5#include "./exceptions.h"
6#include "./locale.h"
8#include "./signature.h"
9#include "./tag.h"
10
11#include "./id3/id3v1tag.h"
12#include "./id3/id3v2tag.h"
13
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
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
51using namespace std;
52using namespace std::placeholders;
53using namespace CppUtilities;
54
60namespace TagParser {
61
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)
95{
96}
97
102 : MediaFileInfo(std::string())
103{
104}
105
109MediaFileInfo::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;
155startParsingSignature:
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 }
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:
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);
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 {
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) {
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) {
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();
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
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 }
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()) {
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()) {
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) {
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
799string_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
826vector<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
882CppUtilities::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
920unordered_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) {
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) {
1174 return true;
1175 default:
1176 return false;
1177 }
1178}
1179
1184{
1185 if (trackCount()) {
1186 return true;
1187 }
1188 switch (m_containerFormat) {
1195 return true;
1196 default:
1197 return false;
1198 }
1199}
1200
1205{
1206 switch (m_containerFormat) {
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
1244const 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
1272vector<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
1290vector<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) {
1421 if (m_container) {
1422 return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1423 }
1424 break;
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) {
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;
1455 if (m_singleTrack) {
1456 return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1457 }
1458 break;
1459 default:;
1460 }
1461 return false;
1462}
1463
1473void 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
1501vector<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
1529void 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
1563std::vector<Tag *> MediaFileInfo::parsedTags() const
1564{
1565 auto res = vector<Tag *>();
1566 parsedTags(res);
1567 return res;
1568}
1569
1574{
1577}
1578
1582void 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 originalPath = path(), 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::createBackupFileCanonical(backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1713 // recreate original file, define buffer variables
1714 outputStream.open(originalPath, 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::handleFailureAfterFileModifiedCanonical(*this, originalPath, 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:75
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 handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
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