Tag Parser 11.5.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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 std::streamoff id3v2Size = 0;
152
153 // read signatrue
154 char buff[16];
155 const char *const buffEnd = buff + sizeof(buff), *buffOffset;
156startParsingSignature:
157 if (progress.isAborted()) {
158 diag.emplace_back(DiagLevel::Information, "Parsing the container format has been aborted.", context);
159 return;
160 }
161 if (size() - containerOffset() >= 16) {
162 stream().seekg(m_containerOffset, ios_base::beg);
163 stream().read(buff, sizeof(buff));
164
165 // skip zero/junk bytes
166 // notes:
167 // - Only skipping 4 or more consecutive zero bytes at this point because some signatures start with up to 4 zero bytes.
168 // - It seems that most players/tools¹ skip junk bytes, at least when reading MP3 files. Hence the tagparser library is following
169 // the same approach. (¹e.g. ffmpeg: "[mp3 @ 0x559e1f4cbd80] Skipping 1670 bytes of junk at 1165.")
170 std::size_t bytesSkipped = 0;
171 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
172 ;
173 if (bytesSkipped >= 4) {
174 skipJunkBytes:
175 m_containerOffset += static_cast<std::streamoff>(bytesSkipped);
176 m_paddingSize += bytesSkipped;
177
178 // give up after 0x800 bytes
179 if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
180 m_containerParsingStatus = ParsingStatus::NotSupported;
181 m_containerFormat = ContainerFormat::Unknown;
182 m_containerOffset = id3v2Size;
183 return;
184 }
185
186 // try again
187 goto startParsingSignature;
188 }
189
190 // parse signature
191 switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
193 // save position of ID3v2 tag
194 m_actualId3v2TagOffsets.push_back(m_containerOffset);
195 if (m_actualId3v2TagOffsets.size() == 2) {
196 diag.emplace_back(DiagLevel::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
197 }
198
199 // read ID3v2 header
200 stream().seekg(m_containerOffset + 5, ios_base::beg);
201 stream().read(buff, 5);
202
203 // set the container offset to skip ID3v2 header
204 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
205 if ((*buff) & 0x10) {
206 // footer present
207 m_containerOffset += 10;
208 }
209 id3v2Size = m_containerOffset;
210
211 // continue reading signature
212 goto startParsingSignature;
213
216 // MP4/QuickTime is handled using Mp4Container instance
217 m_container = make_unique<Mp4Container>(*this, m_containerOffset);
218 try {
219 static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, progress, &m_paddingSize);
220 } catch (const OperationAbortedException &) {
221 diag.emplace_back(DiagLevel::Information, "Validating the MP4 element structure has been aborted.", context);
222 } catch (const Failure &) {
223 m_containerParsingStatus = ParsingStatus::CriticalFailure;
224 }
225 break;
226 }
228 // EBML/Matroska is handled using MatroskaContainer instance
229 auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
230 try {
231 container->parseHeader(diag, progress);
232 if (container->documentType() == "matroska") {
233 m_containerFormat = ContainerFormat::Matroska;
234 } else if (container->documentType() == "webm") {
235 m_containerFormat = ContainerFormat::Webm;
236 }
237 if (isForcingFullParse()) {
238 // validating the element structure of Matroska files takes too long when
239 // parsing big files so do this only when explicitly desired
240 container->validateElementStructure(diag, progress, &m_paddingSize);
241 container->validateIndex(diag, progress);
242 }
243 } catch (const OperationAbortedException &) {
244 diag.emplace_back(DiagLevel::Information, "Validating the Matroska element structure has been aborted.", context);
245 } catch (const Failure &) {
246 m_containerParsingStatus = ParsingStatus::CriticalFailure;
247 }
248 m_container = move(container);
249 break;
250 }
252 // Ogg is handled by OggContainer instance
253 m_container = make_unique<OggContainer>(*this, m_containerOffset);
254 static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(isForcingFullParse());
255 break;
257 // check for magic numbers at odd offsets
258 // -> check for tar (magic number at offset 0x101)
259 if (size() > 0x107) {
260 stream().seekg(0x101);
261 stream().read(buff, 6);
262 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
263 m_containerFormat = ContainerFormat::Tar;
264 break;
265 }
266 }
267 // skip previously determined zero-bytes or try our luck on the next byte
268 if (!bytesSkipped) {
269 ++bytesSkipped;
270 }
271 goto skipJunkBytes;
272 default:;
273 }
274 }
275
276 if (bytesSkippedBeforeContainer) {
277 diag.emplace_back(DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer, " bytes of junk skipped"), context);
278 }
279
280 // set parsing status
281 if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
282 if (m_containerFormat == ContainerFormat::Unknown) {
283 m_containerParsingStatus = ParsingStatus::NotSupported;
284 } else {
285 m_containerParsingStatus = ParsingStatus::Ok;
286 }
287 }
288}
289
302{
303 // skip if tracks already parsed
305 return;
306 }
307 static const string context("parsing tracks");
308
309 try {
310 // parse tracks via container object
311 if (m_container) {
312 m_container->parseTracks(diag, progress);
313 m_tracksParsingStatus = ParsingStatus::Ok;
314 return;
315 }
316
317 // parse tracks via track object for "single-track"-formats
318 switch (m_containerFormat) {
320 m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
321 break;
323 m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
324 break;
326 m_singleTrack = make_unique<IvfStream>(stream(), m_containerOffset);
327 break;
329 m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
330 break;
332 m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
333 break;
334 default:
336 }
337 m_singleTrack->parseHeader(diag, progress);
338
339 // take padding for some "single-track" formats into account
340 switch (m_containerFormat) {
342 m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
343 break;
344 default:;
345 }
346
347 m_tracksParsingStatus = ParsingStatus::Ok;
348
349 } catch (const NotImplementedException &) {
350 diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
351 m_tracksParsingStatus = ParsingStatus::NotSupported;
352 } catch (const OperationAbortedException &) {
353 diag.emplace_back(DiagLevel::Information, "Parsing tracks has been aborted.", context);
354 } catch (const Failure &) {
355 diag.emplace_back(DiagLevel::Critical, "Unable to parse tracks.", context);
356 m_tracksParsingStatus = ParsingStatus::CriticalFailure;
357 }
358}
359
373{
374 // skip if tags already parsed
376 return;
377 }
378 static const string context("parsing tag");
379
380 // check for ID3v1 tag
381 if (size() >= 128) {
382 m_id3v1Tag = make_unique<Id3v1Tag>();
383 try {
384 stream().seekg(-128, ios_base::end);
385 m_id3v1Tag->parse(stream(), diag);
387 } catch (const NoDataFoundException &) {
388 m_id3v1Tag.reset();
389 } catch (const OperationAbortedException &) {
390 diag.emplace_back(DiagLevel::Information, "Parsing ID3v1 tag has been aborted.", context);
391 return;
392 } catch (const Failure &) {
393 m_tagsParsingStatus = ParsingStatus::CriticalFailure;
394 diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
395 }
396 }
397
398 // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
399 m_id3v2Tags.clear();
400 for (const auto offset : m_actualId3v2TagOffsets) {
401 auto id3v2Tag = make_unique<Id3v2Tag>();
402 stream().seekg(offset, ios_base::beg);
403 try {
404 id3v2Tag->parse(stream(), size() - static_cast<std::uint64_t>(offset), diag);
405 m_paddingSize += id3v2Tag->paddingSize();
406 } catch (const NoDataFoundException &) {
407 continue;
408 } catch (const OperationAbortedException &) {
409 diag.emplace_back(DiagLevel::Information, "Parsing ID3v2 tags has been aborted.", context);
410 return;
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, progress);
422 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
423 m_tagsParsingStatus = m_tracksParsingStatus;
424 }
425 return;
426 } else if (m_container) {
427 m_container->parseTags(diag, progress);
428 } else {
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 OperationAbortedException &) {
444 diag.emplace_back(DiagLevel::Information, "Parsing tags from container/streams has been aborted.", context);
445 return;
446 } catch (const Failure &) {
447 m_tagsParsingStatus = ParsingStatus::CriticalFailure;
448 diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
449 }
450}
451
462{
463 // skip if chapters already parsed
465 return;
466 }
467 static const string context("parsing chapters");
468
469 try {
470 // parse chapters via container object
471 if (!m_container) {
473 }
474 m_container->parseChapters(diag, progress);
475 m_chaptersParsingStatus = ParsingStatus::Ok;
476 } catch (const NotImplementedException &) {
477 m_chaptersParsingStatus = ParsingStatus::NotSupported;
478 diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
479 } catch (const Failure &) {
480 m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
481 diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
482 }
483}
484
495{
496 // skip if attachments already parsed
498 return;
499 }
500 static const string context("parsing attachments");
501
502 try {
503 // parse attachments via container object
504 if (!m_container) {
506 }
507 m_container->parseAttachments(diag, progress);
508 m_attachmentsParsingStatus = ParsingStatus::Ok;
509 } catch (const NotImplementedException &) {
510 m_attachmentsParsingStatus = ParsingStatus::NotSupported;
511 diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
512 } catch (const Failure &) {
513 m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
514 diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
515 }
516}
517
525{
526 parseContainerFormat(diag, progress);
527 if (progress.isAborted()) {
528 return;
529 }
530 parseTracks(diag, progress);
531 if (progress.isAborted()) {
532 return;
533 }
534 parseTags(diag, progress);
535 if (progress.isAborted()) {
536 return;
537 }
538 parseChapters(diag, progress);
539 if (progress.isAborted()) {
540 return;
541 }
542 parseAttachments(diag, progress);
543}
544
557{
558 // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
560 return false;
561 }
562
563 // check if tags need to be created/adjusted/removed
564 const auto requiredTargets(settings.requiredTargets);
565 const auto flags(settings.flags);
566 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
567 auto targetsSupported = false;
568 if (areTagsSupported() && m_container) {
569 // container object takes care of tag management
570 if (targetsRequired) {
571 // check whether container supports targets
572 if (m_container->tagCount()) {
573 // all tags in the container should support targets if the first one supports targets
574 targetsSupported = m_container->tag(0)->supportsTarget();
575 } else {
576 // try to create a new tag and check whether targets are supported
577 auto *const tag = m_container->createTag();
578 if (tag && (targetsSupported = tag->supportsTarget())) {
579 tag->setTarget(requiredTargets.front());
580 }
581 }
582 if (targetsSupported) {
583 for (const auto &target : requiredTargets) {
584 m_container->createTag(target);
585 }
586 }
587 } else {
588 // no targets are required -> just ensure that at least one tag is present
589 m_container->createTag();
590 }
591 return true;
592 }
593
594 // no container object present
595 switch (m_containerFormat) {
597 static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
598 break;
599 default:
600 // create ID3 tag(s)
602 switch (containerFormat()) {
607 break;
608 default:
609 return false;
610 }
611 }
612 // create ID3 tags according to id3v2usage and id3v2usage
613 // always create ID3v1 tag -> ensure there is one
614 if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
615 auto *const id3v1Tag = createId3v1Tag();
617 for (const auto &id3v2Tag : id3v2Tags()) {
618 // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
619 id3v1Tag->insertValues(*id3v2Tag, true);
620 // ID3v1 does not support all text encodings which might be used in ID3v2
622 }
623 }
624 }
625 if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
626 // always create ID3v2 tag -> ensure there is one and set version
627 auto *const id3v2Tag = createId3v2Tag();
628 id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
629 if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
630 id3v2Tag->insertValues(*id3v1Tag(), true);
631 }
632 }
633 }
634
637 }
638 // remove ID3 tags according to settings
639 if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
640 // transfer tags to ID3v2 tag before removing
642 id3v2Tags().front()->insertValues(*id3v1Tag(), false);
643 }
645 }
646 if (settings.id3v2usage == TagUsage::Never) {
648 // transfer tags to ID3v1 tag before removing
649 for (const auto &tag : id3v2Tags()) {
650 id3v1Tag()->insertValues(*tag, false);
651 }
652 }
654 } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
655 // set version of ID3v2 tag according user preferences
656 for (const auto &tag : id3v2Tags()) {
657 tag->setVersion(settings.id3v2MajorVersion, 0);
658 }
659 }
660 return true;
661}
662
684{
685 static const string context("making file");
686 diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
687 bool previousParsingSuccessful = true;
688 switch (tagsParsingStatus()) {
691 break;
692 default:
693 previousParsingSuccessful = false;
694 diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
695 }
696 switch (tracksParsingStatus()) {
699 break;
700 default:
701 previousParsingSuccessful = false;
702 diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
703 }
704 if (!previousParsingSuccessful) {
705 throw InvalidDataException();
706 }
707 if (m_container) { // container object takes care
708 // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
709 if (hasId3v1Tag()) {
710 diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
711 }
712 if (hasId3v2Tag()) {
713 diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
714 }
715 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
716 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
717 try {
718 m_container->makeFile(diag, progress);
719 } catch (...) {
720 // since the file might be messed up, invalidate the parsing results
722 throw;
723 }
724 } else { // implementation if no container object is present
725 // assume the file is a MP3 file
726 try {
727 makeMp3File(diag, progress);
728 } catch (...) {
729 // since the file might be messed up, invalidate the parsing results
731 throw;
732 }
733 }
735}
736
750{
751 MediaType mediaType = MediaType::Unknown;
752 unsigned int version = 0;
753 switch (m_containerFormat) {
755 // check for video track or whether only Opus or Speex tracks are present
756 const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
757 if (tracks.empty()) {
758 break;
759 }
760 bool onlyOpus = true, onlySpeex = true;
761 for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
762 if (track->mediaType() == MediaType::Video) {
763 mediaType = MediaType::Video;
764 }
765 if (track->format().general != GeneralMediaFormat::Opus) {
766 onlyOpus = false;
767 }
768 if (track->format().general != GeneralMediaFormat::Speex) {
769 onlySpeex = false;
770 }
771 }
772 if (onlyOpus) {
773 version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
774 } else if (onlySpeex) {
775 version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
776 }
777 break;
778 }
782 break;
784 if (m_singleTrack) {
785 version = m_singleTrack->format().sub;
786 }
787 break;
788 default:;
789 }
790 return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
791}
792
803string_view MediaFileInfo::mimeType() const
804{
805 MediaType mediaType;
806 switch (m_containerFormat) {
811 break;
812 default:
813 mediaType = MediaType::Unknown;
814 }
815 return TagParser::containerMimeType(m_containerFormat, mediaType);
816}
817
830vector<AbstractTrack *> MediaFileInfo::tracks() const
831{
832 vector<AbstractTrack *> res;
833 size_t trackCount = 0;
834 size_t containerTrackCount = 0;
835 if (m_singleTrack) {
836 trackCount = 1;
837 }
838 if (m_container) {
839 trackCount += (containerTrackCount = m_container->trackCount());
840 }
841 res.reserve(trackCount);
842
843 if (m_singleTrack) {
844 res.push_back(m_singleTrack.get());
845 }
846 for (size_t i = 0; i != containerTrackCount; ++i) {
847 res.push_back(m_container->track(i));
848 }
849 return res;
850}
851
861{
863 return false;
864 }
865 if (m_singleTrack && m_singleTrack->mediaType() == type) {
866 return true;
867 } else if (m_container) {
868 for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
869 if (m_container->track(i)->mediaType() == type) {
870 return true;
871 }
872 }
873 }
874 return false;
875}
876
886CppUtilities::TimeSpan MediaFileInfo::duration() const
887{
888 if (m_container) {
889 return m_container->duration();
890 } else if (m_singleTrack) {
891 return m_singleTrack->duration();
892 }
893 return TimeSpan();
894}
895
906{
907 const auto duration = this->duration();
908 if (duration.isNull()) {
909 return 0.0;
910 }
911 return 0.0078125 * static_cast<double>(size()) / duration.totalSeconds();
912}
913
924unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
925{
926 unordered_set<string> res;
927 if (m_container) {
928 for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
929 const AbstractTrack *const track = m_container->track(i);
930 if (type != MediaType::Unknown && track->mediaType() != type) {
931 continue;
932 }
933 if (const auto &language = track->locale().someAbbreviatedName(); !language.empty()) {
934 res.emplace(language);
935 }
936 }
937 } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type)) {
938 if (const auto &language = m_singleTrack->locale().someAbbreviatedName(); !language.empty()) {
939 res.emplace(language);
940 }
941 }
942 return res;
943}
944
957{
958 if (m_container) {
959 const size_t trackCount = m_container->trackCount();
960 vector<string> parts;
961 parts.reserve(trackCount);
962 for (size_t i = 0; i != trackCount; ++i) {
963 const string description(m_container->track(i)->description());
964 if (!description.empty()) {
965 parts.emplace_back(move(description));
966 }
967 }
968 return joinStrings(parts, " / ");
969 } else if (m_singleTrack) {
970 return m_singleTrack->description();
971 }
972 return string();
973}
974
985{
987 return false;
988 }
989 if (m_id3v1Tag) {
990 m_id3v1Tag.reset();
991 return true;
992 }
993 return false;
994}
995
1012{
1014 return nullptr;
1015 }
1016 if (!m_id3v1Tag) {
1017 m_id3v1Tag = make_unique<Id3v1Tag>();
1018 }
1019 return m_id3v1Tag.get();
1020}
1021
1033{
1035 return false;
1036 }
1037 for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1038 if (i->get() == tag) {
1039 m_id3v2Tags.erase(i);
1040 return true;
1041 }
1042 }
1043 return false;
1044}
1045
1055{
1056 if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1057 return false;
1058 }
1059 m_id3v2Tags.clear();
1060 return true;
1061}
1062
1079{
1080 if (m_id3v2Tags.empty()) {
1081 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1082 }
1083 return m_id3v2Tags.front().get();
1084}
1085
1100{
1101 if (!tag) {
1102 return false;
1103 }
1104
1105 // remove tag via container
1106 if (m_container) {
1107 return m_container->removeTag(tag);
1108 }
1109
1110 // remove tag via track for "single-track" formats
1111 if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1112 auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1113 if (flacStream->vorbisComment() == tag) {
1114 return flacStream->removeVorbisComment();
1115 }
1116 }
1117
1118 // remove ID3 tags
1119 if (m_id3v1Tag.get() == tag) {
1120 m_id3v1Tag.reset();
1121 return true;
1122 }
1123 for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1124 if (i->get() == tag) {
1125 m_id3v2Tags.erase(i);
1126 return true;
1127 }
1128 }
1129 return false;
1130}
1131
1139{
1140 if (m_container) {
1141 m_container->removeAllTags();
1142 }
1143 if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1144 static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1145 }
1146 m_id3v1Tag.reset();
1147 m_id3v2Tags.clear();
1148}
1149
1154{
1155 if (m_container && m_container->chapterCount()) {
1156 return true;
1157 }
1158 switch (m_containerFormat) {
1161 return true;
1162 default:
1163 return false;
1164 }
1165}
1166
1171{
1172 if (m_container && m_container->attachmentCount()) {
1173 return true;
1174 }
1175 switch (m_containerFormat) {
1178 return true;
1179 default:
1180 return false;
1181 }
1182}
1183
1188{
1189 if (trackCount()) {
1190 return true;
1191 }
1192 switch (m_containerFormat) {
1199 return true;
1200 default:
1201 return false;
1202 }
1203}
1204
1209{
1210 switch (m_containerFormat) {
1219 // these container formats are supported
1220 return true;
1221 default:
1222 // the container format is unsupported
1223 // -> an ID3 tag might be already present, in this case the tags are considered supported
1224 return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1225 }
1226}
1227
1234{
1235 // simply return the first tag here since MP4 files never contain multiple tags
1236 return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1237 && m_container->tagCount() > 0
1238 ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1239 : nullptr;
1240}
1241
1248const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1249{
1250 // matroska files might contain multiple tags (targeting different scopes)
1251 if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1252 return static_cast<MatroskaContainer *>(m_container.get())->tags();
1253 } else {
1254 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1255 return empty;
1256 }
1257}
1258
1265{
1266 return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1267 ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1268 : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1269}
1270
1276vector<AbstractChapter *> MediaFileInfo::chapters() const
1277{
1278 vector<AbstractChapter *> res;
1279 if (m_container) {
1280 const size_t count = m_container->chapterCount();
1281 res.reserve(count);
1282 for (size_t i = 0; i != count; ++i) {
1283 res.push_back(m_container->chapter(i));
1284 }
1285 }
1286 return res;
1287}
1288
1294vector<AbstractAttachment *> MediaFileInfo::attachments() const
1295{
1296 vector<AbstractAttachment *> res;
1297 if (m_container) {
1298 const size_t count = m_container->attachmentCount();
1299 res.reserve(count);
1300 for (size_t i = 0; i != count; ++i) {
1301 res.push_back(m_container->attachment(i));
1302 }
1303 }
1304 return res;
1305}
1306
1319{
1320 m_containerParsingStatus = ParsingStatus::NotParsedYet;
1321 m_containerFormat = ContainerFormat::Unknown;
1322 m_containerOffset = 0;
1323 m_paddingSize = 0;
1324 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1325 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1326 m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1327 m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1328 m_id3v1Tag.reset();
1329 m_id3v2Tags.clear();
1330 m_actualId3v2TagOffsets.clear();
1331 m_fileStructureFlags = MediaFileStructureFlags::None;
1332 m_container.reset();
1333 m_singleTrack.reset();
1334}
1335
1353{
1354 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1355 if (begin == end) {
1356 return;
1357 }
1358 Id3v2Tag &first = **begin;
1359 auto isecond = begin + 1;
1360 if (isecond == end) {
1361 return;
1362 }
1363 for (auto i = isecond; i != end; ++i) {
1364 first.insertFields(**i, false);
1365 }
1366 m_id3v2Tags.erase(isecond, end);
1367}
1368
1380{
1382 return false;
1383 }
1386}
1387
1399{
1401 return false;
1402 }
1405}
1406
1422{
1423 switch (m_containerFormat) {
1425 if (m_container) {
1426 return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1427 }
1428 break;
1430 if (m_singleTrack) {
1431 return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1432 }
1433 break;
1434 default:;
1435 }
1436 return nullptr;
1437}
1438
1449{
1450 switch (m_containerFormat) {
1452 if (m_container) {
1453 bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1454 static_cast<OggContainer *>(m_container.get())->removeAllTags();
1455 return hadTags;
1456 }
1457 break;
1459 if (m_singleTrack) {
1460 return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1461 }
1462 break;
1463 default:;
1464 }
1465 return false;
1466}
1467
1477void MediaFileInfo::tags(std::vector<Tag *> &tags) const
1478{
1479 if (hasId3v1Tag()) {
1480 tags.push_back(m_id3v1Tag.get());
1481 }
1482 for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1483 tags.push_back(tag.get());
1484 }
1485 if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1486 if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1487 tags.push_back(vorbisComment);
1488 }
1489 }
1490 if (m_container) {
1491 for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1492 tags.push_back(m_container->tag(i));
1493 }
1494 }
1495}
1496
1505vector<Tag *> MediaFileInfo::tags() const
1506{
1507 auto res = vector<Tag *>();
1508 tags(res);
1509 return res;
1510}
1511
1519{
1520 return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1521 || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1522}
1523
1533void MediaFileInfo::parsedTags(std::vector<Tag *> &tags) const
1534{
1535 if (hasId3v1Tag() && m_id3v1Tag->size()) {
1536 tags.push_back(m_id3v1Tag.get());
1537 }
1538 for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1539 if (tag->size()) {
1540 tags.push_back(tag.get());
1541 }
1542 }
1543 if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1544 if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1545 if (vorbisComment->size()) {
1546 tags.push_back(vorbisComment);
1547 }
1548 }
1549 }
1550 if (m_container) {
1551 for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1552 if (auto *const tag = m_container->tag(i); tag->size()) {
1553 tags.push_back(tag);
1554 }
1555 }
1556 }
1557}
1558
1567std::vector<Tag *> MediaFileInfo::parsedTags() const
1568{
1569 auto res = vector<Tag *>();
1570 parsedTags(res);
1571 return res;
1572}
1573
1578{
1581}
1582
1586void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1587{
1588 static const string context("making MP3/FLAC file");
1589
1590 // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1591 if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1592 && m_containerFormat != ContainerFormat::Flac) {
1593 // alter ID3v1 tag
1594 if (!m_id3v1Tag) {
1595 // remove ID3v1 tag
1596 if (!(m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag)) {
1597 diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1598 return;
1599 }
1600 progress.updateStep("Removing ID3v1 tag ...");
1601 stream().close();
1602 if (truncate(BasicFileInfo::pathForOpen(path()).data(), static_cast<std::streamoff>(size() - 128)) == 0) {
1603 reportSizeChanged(size() - 128);
1604 } else {
1605 diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag.", context);
1606 throw std::ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
1607 }
1608 return;
1609 } else {
1610 // add or update ID3v1 tag
1611 if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1612 progress.updateStep("Updating existing ID3v1 tag ...");
1613 // ensure the file is still open / not readonly
1614 open();
1615 stream().seekp(-128, ios_base::end);
1616 try {
1617 m_id3v1Tag->make(stream(), diag);
1618 } catch (const Failure &) {
1619 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1620 }
1621 } else {
1622 progress.updateStep("Adding new ID3v1 tag ...");
1623 // ensure the file is still open / not readonly
1624 open();
1625 stream().seekp(0, ios_base::end);
1626 try {
1627 m_id3v1Tag->make(stream(), diag);
1628 } catch (const Failure &) {
1629 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1630 }
1631 }
1632 // prevent deferring final write operations (to catch and handle possible errors here)
1633 stream().flush();
1634 }
1635 return;
1636 }
1637
1638 // ID3v2 needs to be modified
1639 FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1640 progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1641
1642 // prepare ID3v2 tags
1643 vector<Id3v2TagMaker> makers;
1644 makers.reserve(m_id3v2Tags.size());
1645 std::uint64_t tagsSize = 0;
1646 for (auto &tag : m_id3v2Tags) {
1647 try {
1648 makers.emplace_back(tag->prepareMaking(diag));
1649 tagsSize += makers.back().requiredSize();
1650 } catch (const Failure &) {
1651 }
1652 }
1653
1654 // determine stream offset and make track/format specific metadata
1655 std::uint32_t streamOffset; // where the actual stream starts
1656 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1657 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1658 std::streamoff startOfLastMetaDataBlock;
1659 if (flacStream) {
1660 // if it is a raw FLAC stream, make FLAC metadata
1661 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1662 tagsSize += static_cast<std::uint64_t>(flacMetaData.tellp());
1663 streamOffset = flacStream->streamOffset();
1664 } else {
1665 // make no further metadata, just use the container offset as stream offset
1666 streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1667 }
1668
1669 // check whether rewrite is required
1670 bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1671 size_t padding = 0;
1672 if (!rewriteRequired) {
1673 // rewriting is not forced and new tag is not too big for available space
1674 // -> calculate new padding
1675 padding = streamOffset - tagsSize;
1676 // -> check whether the new padding matches specifications
1677 if (padding < minPadding() || padding > maxPadding()) {
1678 rewriteRequired = true;
1679 }
1680 }
1681 if (makers.empty() && !flacStream) {
1682 // an ID3v2 tag is not written and it is not a FLAC stream
1683 // -> can't include padding
1684 if (padding) {
1685 // but padding would be present -> need to rewrite
1686 padding = 0; // can't write the preferred padding despite rewriting
1687 rewriteRequired = true;
1688 }
1689 } else if (rewriteRequired) {
1690 // rewriting is forced or new ID3v2 tag is too big for available space
1691 // -> use preferred padding when rewriting anyways
1692 padding = preferredPadding();
1693 } else if (makers.empty() && flacStream && padding && padding < 4) {
1694 // no ID3v2 tag -> must include padding in FLAC stream
1695 // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1696 padding = preferredPadding();
1697 rewriteRequired = true;
1698 }
1699 if (rewriteRequired && flacStream && makers.empty() && padding) {
1700 // the first 4 byte of FLAC padding actually don't count because these
1701 // can not be used for additional meta data
1702 padding += 4;
1703 }
1704 progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1705
1706 // setup stream(s) for writing
1707 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1708 string originalPath = path(), backupPath;
1709 NativeFileStream &outputStream = stream();
1710 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1711
1712 if (rewriteRequired) {
1713 if (m_saveFilePath.empty()) {
1714 // move current file to temp dir and reopen it as backupStream, recreate original file
1715 try {
1716 BackupHelper::createBackupFileCanonical(backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1717 // recreate original file, define buffer variables
1718 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1719 } catch (const std::ios_base::failure &failure) {
1720 diag.emplace_back(
1721 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1722 throw;
1723 }
1724 } else {
1725 // open the current file as backupStream and create a new outputStream at the specified "save file path"
1726 try {
1727 close();
1728 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1729 backupStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::binary);
1730 outputStream.open(BasicFileInfo::pathForOpen(m_saveFilePath).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1731 } catch (const std::ios_base::failure &failure) {
1732 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1733 throw;
1734 }
1735 }
1736
1737 } else { // !rewriteRequired
1738 // reopen original file to ensure it is opened for writing
1739 try {
1740 close();
1741 outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::out | ios_base::binary);
1742 } catch (const std::ios_base::failure &failure) {
1743 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1744 throw;
1745 }
1746 }
1747 // TODO: fix code duplication
1748
1749 // start actual writing
1750 try {
1751 // ensure we can cast padding safely to uint32
1752 if (padding > numeric_limits<std::uint32_t>::max()) {
1753 padding = numeric_limits<std::uint32_t>::max();
1754 diag.emplace_back(
1755 DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1756 }
1757
1758 if (!makers.empty()) {
1759 // write ID3v2 tags
1760 progress.updateStep("Writing ID3v2 tag ...");
1761 for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1762 i->make(outputStream, 0, diag);
1763 }
1764 // include padding into the last ID3v2 tag
1765 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1766 }
1767
1768 if (flacStream) {
1769 if (padding && startOfLastMetaDataBlock) {
1770 // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1771 flacMetaData.seekg(startOfLastMetaDataBlock);
1772 flacMetaData.seekp(startOfLastMetaDataBlock);
1773 flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1774 flacMetaData.seekg(0);
1775 }
1776
1777 // write FLAC metadata
1778 outputStream << flacMetaData.rdbuf();
1779
1780 // write padding
1781 if (padding) {
1782 flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding), true, diag);
1783 }
1784 }
1785
1786 if (makers.empty() && !flacStream) {
1787 // just write padding (however, padding should be set to 0 in this case?)
1788 for (; padding; --padding) {
1789 outputStream.put(0);
1790 }
1791 }
1792
1793 // copy / skip actual stream data
1794 // -> determine media data size
1795 std::uint64_t mediaDataSize = size() - streamOffset;
1796 if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1797 mediaDataSize -= 128;
1798 }
1799
1800 if (rewriteRequired) {
1801 // copy data from original file
1802 switch (m_containerFormat) {
1804 progress.updateStep("Writing MPEG audio frames ...");
1805 break;
1806 default:
1807 progress.updateStep("Writing data ...");
1808 }
1809 backupStream.seekg(static_cast<streamoff>(streamOffset));
1810 CopyHelper<0x4000> copyHelper;
1811 copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&AbortableProgressFeedback::isAborted, ref(progress)),
1812 bind(&AbortableProgressFeedback::updateStepPercentage, ref(progress), _1));
1813 } else {
1814 // just skip actual stream data
1815 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1816 }
1817
1818 // write ID3v1 tag
1819 if (m_id3v1Tag) {
1820 progress.updateStep("Writing ID3v1 tag ...");
1821 try {
1822 m_id3v1Tag->make(stream(), diag);
1823 } catch (const Failure &) {
1824 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1825 }
1826 }
1827
1828 // handle streams
1829 if (rewriteRequired) {
1830 // report new size
1831 reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1832 // "save as path" is now the regular path
1833 if (!saveFilePath().empty()) {
1835 m_saveFilePath.clear();
1836 }
1837 // prevent deferring final write operations (to catch and handle possible errors here); stream is useless for further
1838 // usage anyways because it is write-only
1839 outputStream.close();
1840 } else {
1841 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1842 if (newSize < size()) {
1843 // file is smaller after the modification -> truncate
1844 // -> prevent deferring final write operations
1845 outputStream.close();
1846 // -> truncate file
1847 if (truncate(BasicFileInfo::pathForOpen(path()).data(), static_cast<streamoff>(newSize)) == 0) {
1848 reportSizeChanged(newSize);
1849 } else {
1850 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file.", context);
1851 }
1852 } else {
1853 // file is longer after the modification
1854 // -> prevent deferring final write operations (to catch and handle possible errors here)
1855 outputStream.flush();
1856 // -> report new size
1857 reportSizeChanged(newSize);
1858 }
1859 }
1860
1861 } catch (...) {
1862 BackupHelper::handleFailureAfterFileModifiedCanonical(*this, originalPath, backupPath, outputStream, backupStream, diag, context);
1863 }
1864}
1865
1866} // 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...
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:260
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:76
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:134
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:163
std::uint64_t size() const
Returns the size the tag within the file it is parsed from in bytes.
Definition: tag.h:230
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.
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
ElementPosition
Definition: settings.h:13
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:263
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize)
Definition: signature.h:81
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:515
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