Tag Parser 12.1.0
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#include <c++utilities/io/path.h>
41
42#include <algorithm>
43#include <cstdint>
44#include <cstdio>
45#include <filesystem>
46#include <functional>
47#include <iomanip>
48#include <ios>
49#include <memory>
50#include <system_error>
51
52using namespace std;
53using namespace std::placeholders;
54using namespace CppUtilities;
55
61namespace TagParser {
62
65
82 : BasicFileInfo(std::move(path))
83 , m_containerParsingStatus(ParsingStatus::NotParsedYet)
84 , m_containerFormat(ContainerFormat::Unknown)
85 , m_containerOffset(0)
86 , m_paddingSize(0)
87 , m_effectiveSize(0)
88 , m_fileStructureFlags(MediaFileStructureFlags::None)
89 , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
90 , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
91 , m_chaptersParsingStatus(ParsingStatus::NotParsedYet)
92 , m_attachmentsParsingStatus(ParsingStatus::NotParsedYet)
93 , m_minPadding(0)
94 , m_maxPadding(0)
95 , m_preferredPadding(0)
96 , m_tagPosition(ElementPosition::BeforeData)
97 , m_indexPosition(ElementPosition::BeforeData)
100 , m_maxFullParseSize(0x3200000)
101{
102}
103
108 : MediaFileInfo(std::string())
109{
110}
111
115MediaFileInfo::MediaFileInfo(std::string_view path)
116 : MediaFileInfo(std::string(path))
117{
118}
119
126
141{
142 CPP_UTILITIES_UNUSED(progress)
143
144 // skip if container format already parsed
146 return;
147 }
148
149 static const string context("parsing file header");
150 open(); // ensure the file is open
151 m_containerFormat = ContainerFormat::Unknown;
152
153 // file size
154 m_paddingSize = 0;
155 m_containerOffset = 0;
156 std::size_t bytesSkippedBeforeContainer = 0;
157 std::streamoff id3v2Size = 0;
158
159 // read signatrue
160 char buff[16];
161 const char *const buffEnd = buff + sizeof(buff), *buffOffset;
162startParsingSignature:
163 if (progress.isAborted()) {
164 diag.emplace_back(DiagLevel::Information, "Parsing the container format has been aborted.", context);
165 return;
166 }
167 if (size() - containerOffset() >= 16) {
168 stream().seekg(m_containerOffset, ios_base::beg);
169 stream().read(buff, sizeof(buff));
170
171 // skip zero/junk bytes
172 // notes:
173 // - Only skipping 4 or more consecutive zero bytes at this point because some signatures start with up to 4 zero bytes.
174 // - It seems that most players/tools¹ skip junk bytes, at least when reading MP3 files. Hence the tagparser library is following
175 // the same approach. (¹e.g. ffmpeg: "[mp3 @ 0x559e1f4cbd80] Skipping 1670 bytes of junk at 1165.")
176 std::size_t bytesSkipped = 0;
177 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
178 ;
179 if (bytesSkipped >= 4) {
180 skipJunkBytes:
181 m_containerOffset += static_cast<std::streamoff>(bytesSkipped);
182 m_paddingSize += bytesSkipped;
183
184 // give up after 0x800 bytes
185 if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
186 m_containerParsingStatus = ParsingStatus::NotSupported;
187 m_containerFormat = ContainerFormat::Unknown;
188 m_containerOffset = id3v2Size;
189 return;
190 }
191
192 // try again
193 goto startParsingSignature;
194 }
195
196 // parse signature
197 switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
199 // save position of ID3v2 tag
200 m_actualId3v2TagOffsets.push_back(m_containerOffset);
201 if (m_actualId3v2TagOffsets.size() == 2) {
202 diag.emplace_back(DiagLevel::Warning, "There is more than just one ID3v2 header at the beginning of the file.", context);
203 }
204
205 // read ID3v2 header
206 stream().seekg(m_containerOffset + 5, ios_base::beg);
207 stream().read(buff, 5);
208
209 // set the container offset to skip ID3v2 header
210 m_containerOffset += toNormalInt(BE::toInt<std::uint32_t>(buff + 1)) + 10;
211 if ((*buff) & 0x10) {
212 // footer present
213 m_containerOffset += 10;
214 }
215 id3v2Size = m_containerOffset;
216
217 // continue reading signature
218 goto startParsingSignature;
219
222 // MP4/QuickTime is handled using Mp4Container instance
223 m_container = make_unique<Mp4Container>(*this, m_containerOffset);
224 try {
225 static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, progress, &m_paddingSize);
226 } catch (const OperationAbortedException &) {
227 diag.emplace_back(DiagLevel::Information, "Validating the MP4 element structure has been aborted.", context);
228 } catch (const Failure &) {
229 m_containerParsingStatus = ParsingStatus::CriticalFailure;
230 }
231 break;
232 }
234 // EBML/Matroska is handled using MatroskaContainer instance
235 auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
236 try {
237 container->parseHeader(diag, progress);
238 if (container->documentType() == "matroska") {
239 m_containerFormat = ContainerFormat::Matroska;
240 } else if (container->documentType() == "webm") {
241 m_containerFormat = ContainerFormat::Webm;
242 }
243 if (isForcingFullParse()) {
244 // validating the element structure of Matroska files takes too long when
245 // parsing big files so do this only when explicitly desired
246 container->validateElementStructure(diag, progress, &m_paddingSize);
247 container->validateIndex(diag, progress);
248 }
249 } catch (const OperationAbortedException &) {
250 diag.emplace_back(DiagLevel::Information, "Validating the Matroska element structure has been aborted.", context);
251 } catch (const Failure &) {
252 m_containerParsingStatus = ParsingStatus::CriticalFailure;
253 }
254 m_container = std::move(container);
255 break;
256 }
258 // Ogg is handled by OggContainer instance
259 m_container = make_unique<OggContainer>(*this, m_containerOffset);
260 static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(isForcingFullParse());
261 break;
264 // skip APE tag if the specified size makes sense at all
265 if (m_containerFormat == ContainerFormat::ApeTag) {
266 if (const auto apeEnd = m_containerOffset + 32 + LE::toUInt32(buff + 12); apeEnd <= static_cast<std::streamoff>(size())) {
267 // take record of APE tag
268 diag.emplace_back(DiagLevel::Critical,
269 argsToString("Found an APE tag at the beginning of the file at offset ", m_containerOffset,
270 ". This tag format is not supported and the tag will therefore be ignored. It will NOT be preserved when saving as "
271 "placing an APE tag at the beginning of a file is strongly unrecommended."),
272 context);
273 // continue reading signature
274 m_containerOffset = apeEnd;
275 goto startParsingSignature;
276 }
277 m_containerFormat = ContainerFormat::Unknown;
278 }
279
280 // check for magic numbers at odd offsets
281 // -> check for tar (magic number at offset 0x101)
282 if (size() > 0x107) {
283 stream().seekg(0x101);
284 stream().read(buff, 6);
285 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
286 m_containerFormat = ContainerFormat::Tar;
287 break;
288 }
289 }
290 // skip previously determined zero-bytes or try our luck on the next byte
291 if (!bytesSkipped) {
292 ++bytesSkipped;
293 }
294 goto skipJunkBytes;
295 default:;
296 }
297 }
298
299 if (bytesSkippedBeforeContainer) {
300 diag.emplace_back(DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer, " bytes of junk skipped"), context);
301 }
302
303 // set parsing status
304 if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
305 if (m_containerFormat == ContainerFormat::Unknown) {
306 m_containerParsingStatus = ParsingStatus::NotSupported;
307 } else {
308 m_containerParsingStatus = ParsingStatus::Ok;
309 }
310 }
311}
312
325{
326 // skip if tracks already parsed
328 return;
329 }
330 static const string context("parsing tracks");
331
332 try {
333 // parse tracks via container object
334 if (m_container) {
335 m_container->parseTracks(diag, progress);
336 m_tracksParsingStatus = ParsingStatus::Ok;
337 return;
338 }
339
340 // parse tracks via track object for "single-track"-formats
341 switch (m_containerFormat) {
343 m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
344 break;
346 m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
347 break;
349 m_singleTrack = make_unique<IvfStream>(stream(), m_containerOffset);
350 break;
352 m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
353 break;
355 m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
356 break;
357 default:
359 }
360 if (m_containerFormat != ContainerFormat::Flac) {
361 // ensure the effective size has been determined
362 // note: This is not required for FLAC and should also be avoided as parseTags() will invoke
363 // parseTracks() when dealing with FLAC files.
364 parseTags(diag, progress);
365 m_singleTrack->setSize(m_effectiveSize);
366 }
367 m_singleTrack->parseHeader(diag, progress);
368
369 // take padding for some "single-track" formats into account
370 switch (m_containerFormat) {
372 m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
373 break;
374 default:;
375 }
376
377 m_tracksParsingStatus = ParsingStatus::Ok;
378
379 } catch (const NotImplementedException &) {
380 diag.emplace_back(DiagLevel::Information, "Parsing tracks is not implemented for the container format of the file.", context);
381 m_tracksParsingStatus = ParsingStatus::NotSupported;
382 } catch (const OperationAbortedException &) {
383 diag.emplace_back(DiagLevel::Information, "Parsing tracks has been aborted.", context);
384 } catch (const Failure &) {
385 diag.emplace_back(DiagLevel::Critical, "Unable to parse tracks.", context);
386 m_tracksParsingStatus = ParsingStatus::CriticalFailure;
387 }
388}
389
403{
404 // skip if tags already parsed
406 return;
407 }
408 static const string context("parsing tag");
409
410 // check for ID3v1 tag
411 auto effectiveSize = static_cast<std::streamoff>(size());
412 if (effectiveSize >= 128) {
413 m_id3v1Tag = make_unique<Id3v1Tag>();
414 try {
415 stream().seekg(effectiveSize - 128, std::ios_base::beg);
416 m_id3v1Tag->parse(stream(), diag);
418 effectiveSize -= 128;
419 } catch (const NoDataFoundException &) {
420 m_id3v1Tag.reset();
421 } catch (const OperationAbortedException &) {
422 diag.emplace_back(DiagLevel::Information, "Parsing ID3v1 tag has been aborted.", context);
423 return;
424 } catch (const Failure &) {
425 m_tagsParsingStatus = ParsingStatus::CriticalFailure;
426 diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v1 tag.", context);
427 }
428 }
429
430 // check for APE tag at the end of the file (APE tags a the beginning are already covered when parsing the container format)
431 if (constexpr auto apeHeaderSize = 32; effectiveSize >= apeHeaderSize) {
432 const auto footerOffset = effectiveSize - apeHeaderSize;
433 char buffer[apeHeaderSize];
434 stream().seekg(footerOffset, std::ios_base::beg);
435 stream().read(buffer, sizeof(buffer));
436 if (BE::toInt<std::uint64_t>(buffer) == 0x4150455441474558ul /* APETAGEX */) {
437 // take record of APE tag
438 const auto tagSize = static_cast<std::streamoff>(LE::toInt<std::uint32_t>(buffer + 12));
439 const auto flags = LE::toInt<std::uint32_t>(buffer + 20);
440 // subtract tag size (footer size and contents) from effective size
441 if (tagSize <= effectiveSize) {
442 effectiveSize -= tagSize;
443 }
444 // subtract header size (not included in tag size) from effective size if flags indicate presence of header
445 if ((flags & 0x80000000u) && (apeHeaderSize <= effectiveSize)) {
446 effectiveSize -= apeHeaderSize;
447 }
448 diag.emplace_back(DiagLevel::Warning,
449 argsToString("Found an APE tag at the end of the file at offset ", (footerOffset - tagSize),
450 ". This tag format is not supported and the tag will therefore be ignored. It will be preserved when saving as-is."),
451 context);
452 }
453 }
454
455 // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
456 m_id3v2Tags.clear();
457 for (const auto offset : m_actualId3v2TagOffsets) {
458 auto id3v2Tag = make_unique<Id3v2Tag>();
459 stream().seekg(offset, ios_base::beg);
460 try {
461 id3v2Tag->parse(stream(), size() - static_cast<std::uint64_t>(offset), diag);
462 m_paddingSize += id3v2Tag->paddingSize();
463 } catch (const NoDataFoundException &) {
464 continue;
465 } catch (const OperationAbortedException &) {
466 diag.emplace_back(DiagLevel::Information, "Parsing ID3v2 tags has been aborted.", context);
467 return;
468 } catch (const Failure &) {
469 m_tagsParsingStatus = ParsingStatus::CriticalFailure;
470 diag.emplace_back(DiagLevel::Critical, "Unable to parse ID3v2 tag.", context);
471 }
472 m_id3v2Tags.emplace_back(id3v2Tag.release());
473 }
474
475 // compute effective size
476 m_effectiveSize = static_cast<std::uint64_t>(effectiveSize - m_containerOffset);
477
478 // check for tags in tracks (FLAC only) or via container object
479 try {
480 if (m_containerFormat == ContainerFormat::Flac) {
481 parseTracks(diag, progress);
482 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
483 m_tagsParsingStatus = m_tracksParsingStatus;
484 }
485 return;
486 } else if (m_container) {
487 m_container->parseTags(diag, progress);
488 } else if (m_containerFormat != ContainerFormat::MpegAudioFrames) {
490 }
491
492 // set status, but do not override error/unsupported status form ID3 tags here
493 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
494 m_tagsParsingStatus = ParsingStatus::Ok;
495 }
496
497 } catch (const NotImplementedException &) {
498 // set status to not supported, but do not override parsing status from ID3 tags here
499 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
500 m_tagsParsingStatus = ParsingStatus::NotSupported;
501 }
502 diag.emplace_back(DiagLevel::Information, "Parsing tags is not implemented for the container format of the file.", context);
503 } catch (const OperationAbortedException &) {
504 diag.emplace_back(DiagLevel::Information, "Parsing tags from container/streams has been aborted.", context);
505 return;
506 } catch (const Failure &) {
507 m_tagsParsingStatus = ParsingStatus::CriticalFailure;
508 diag.emplace_back(DiagLevel::Critical, "Unable to parse tag.", context);
509 }
510}
511
522{
523 // skip if chapters already parsed
525 return;
526 }
527 static const string context("parsing chapters");
528
529 try {
530 // parse chapters via container object
531 if (!m_container) {
533 }
534 m_container->parseChapters(diag, progress);
535 m_chaptersParsingStatus = ParsingStatus::Ok;
536 } catch (const NotImplementedException &) {
537 m_chaptersParsingStatus = ParsingStatus::NotSupported;
538 diag.emplace_back(DiagLevel::Information, "Parsing chapters is not implemented for the container format of the file.", context);
539 } catch (const Failure &) {
540 m_chaptersParsingStatus = ParsingStatus::CriticalFailure;
541 diag.emplace_back(DiagLevel::Critical, "Unable to parse chapters.", context);
542 }
543}
544
555{
556 // skip if attachments already parsed
558 return;
559 }
560 static const string context("parsing attachments");
561
562 try {
563 // parse attachments via container object
564 if (!m_container) {
566 }
567 m_container->parseAttachments(diag, progress);
568 m_attachmentsParsingStatus = ParsingStatus::Ok;
569 } catch (const NotImplementedException &) {
570 m_attachmentsParsingStatus = ParsingStatus::NotSupported;
571 diag.emplace_back(DiagLevel::Information, "Parsing attachments is not implemented for the container format of the file.", context);
572 } catch (const Failure &) {
573 m_attachmentsParsingStatus = ParsingStatus::CriticalFailure;
574 diag.emplace_back(DiagLevel::Critical, "Unable to parse attachments.", context);
575 }
576}
577
585{
586 parseContainerFormat(diag, progress);
587 if (progress.isAborted()) {
588 return;
589 }
590 parseTracks(diag, progress);
591 if (progress.isAborted()) {
592 return;
593 }
594 parseTags(diag, progress);
595 if (progress.isAborted()) {
596 return;
597 }
598 parseChapters(diag, progress);
599 if (progress.isAborted()) {
600 return;
601 }
602 parseAttachments(diag, progress);
603}
604
617{
618 // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
620 return false;
621 }
622
623 // check if tags need to be created/adjusted/removed
624 const auto requiredTargets(settings.requiredTargets);
625 const auto flags(settings.flags);
626 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
627 auto targetsSupported = false;
628 if (areTagsSupported() && m_container) {
629 // container object takes care of tag management
630 if (targetsRequired) {
631 // check whether container supports targets
632 if (m_container->tagCount()) {
633 // all tags in the container should support targets if the first one supports targets
634 targetsSupported = m_container->tag(0)->supportsTarget();
635 } else {
636 // try to create a new tag and check whether targets are supported
637 auto *const tag = m_container->createTag();
638 if (tag && (targetsSupported = tag->supportsTarget())) {
639 tag->setTarget(requiredTargets.front());
640 }
641 }
642 if (targetsSupported) {
643 for (const auto &target : requiredTargets) {
644 m_container->createTag(target);
645 }
646 }
647 } else {
648 // no targets are required -> just ensure that at least one tag is present
649 m_container->createTag();
650 }
651 return true;
652 }
653
654 // no container object present
655 switch (m_containerFormat) {
657 static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
658 break;
659 default:
660 // create ID3 tag(s)
662 switch (containerFormat()) {
667 break;
668 default:
669 return false;
670 }
671 }
672 // create ID3 tags according to id3v2usage and id3v2usage
673 // always create ID3v1 tag -> ensure there is one
674 if (settings.id3v1usage == TagUsage::Always && !id3v1Tag()) {
675 auto *const id3v1Tag = createId3v1Tag();
677 for (const auto &id3v2Tag : id3v2Tags()) {
678 // overwrite existing values to ensure default ID3v1 genre "Blues" is updated as well
679 id3v1Tag->insertValues(*id3v2Tag, true);
680 // ID3v1 does not support all text encodings which might be used in ID3v2
682 }
683 }
684 }
685 if (settings.id3v2usage == TagUsage::Always && !hasId3v2Tag()) {
686 // always create ID3v2 tag -> ensure there is one and set version
687 auto *const id3v2Tag = createId3v2Tag();
688 id3v2Tag->setVersion(settings.id3v2MajorVersion, 0);
689 if ((flags & TagCreationFlags::Id3InitOnCreate) && id3v1Tag()) {
690 id3v2Tag->insertValues(*id3v1Tag(), true);
691 }
692 }
693 }
694
697 }
698 // remove ID3 tags according to settings
699 if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
700 // transfer tags to ID3v2 tag before removing
702 id3v2Tags().front()->insertValues(*id3v1Tag(), false);
703 }
705 }
706 if (settings.id3v2usage == TagUsage::Never) {
708 // transfer tags to ID3v1 tag before removing
709 for (const auto &tag : id3v2Tags()) {
710 id3v1Tag()->insertValues(*tag, false);
711 }
712 }
714 } else if (!(flags & TagCreationFlags::KeepExistingId3v2Version)) {
715 // set version of ID3v2 tag according user preferences
716 for (const auto &tag : id3v2Tags()) {
717 tag->setVersion(settings.id3v2MajorVersion, 0);
718 }
719 }
720 return true;
721}
722
744{
745 static const string context("making file");
746 diag.emplace_back(DiagLevel::Information, "Changes are about to be applied.", context);
747 bool previousParsingSuccessful = true;
748 switch (tagsParsingStatus()) {
751 break;
752 default:
753 previousParsingSuccessful = false;
754 diag.emplace_back(DiagLevel::Critical, "Tags have to be parsed without critical errors before changes can be applied.", context);
755 }
756 switch (tracksParsingStatus()) {
759 break;
760 default:
761 previousParsingSuccessful = false;
762 diag.emplace_back(DiagLevel::Critical, "Tracks have to be parsed without critical errors before changes can be applied.", context);
763 }
764 if (!previousParsingSuccessful) {
765 throw InvalidDataException();
766 }
767 if (m_container) { // container object takes care
768 // ID3 tags can not be applied in this case -> add warnings if ID3 tags have been assigned
769 if (hasId3v1Tag()) {
770 diag.emplace_back(DiagLevel::Warning, "Assigned ID3v1 tag can't be attached and will be ignored.", context);
771 }
772 if (hasId3v2Tag()) {
773 diag.emplace_back(DiagLevel::Warning, "Assigned ID3v2 tag can't be attached and will be ignored.", context);
774 }
775 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
776 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
777 try {
778 m_container->makeFile(diag, progress);
779 } catch (...) {
780 // since the file might be messed up, invalidate the parsing results
782 throw;
783 }
784 } else { // implementation if no container object is present
785 // assume the file is a MP3 file
786 try {
787 makeMp3File(diag, progress);
788 } catch (...) {
789 // since the file might be messed up, invalidate the parsing results
791 throw;
792 }
793 }
795}
796
810{
811 MediaType mediaType = MediaType::Unknown;
812 unsigned int version = 0;
813 switch (m_containerFormat) {
815 // check for video track or whether only Opus or Speex tracks are present
816 const auto &tracks = static_cast<OggContainer *>(m_container.get())->tracks();
817 if (tracks.empty()) {
818 break;
819 }
820 bool onlyOpus = true, onlySpeex = true;
821 for (const auto &track : static_cast<OggContainer *>(m_container.get())->tracks()) {
822 if (track->mediaType() == MediaType::Video) {
823 mediaType = MediaType::Video;
824 }
825 if (track->format().general != GeneralMediaFormat::Opus) {
826 onlyOpus = false;
827 }
828 if (track->format().general != GeneralMediaFormat::Speex) {
829 onlySpeex = false;
830 }
831 }
832 if (onlyOpus) {
833 version = static_cast<unsigned int>(GeneralMediaFormat::Opus);
834 } else if (onlySpeex) {
835 version = static_cast<unsigned int>(GeneralMediaFormat::Speex);
836 }
837 break;
838 }
842 break;
844 if (m_singleTrack) {
845 version = m_singleTrack->format().sub;
846 }
847 break;
848 default:;
849 }
850 return TagParser::containerFormatAbbreviation(m_containerFormat, mediaType, version);
851}
852
863string_view MediaFileInfo::mimeType() const
864{
865 MediaType mediaType;
866 switch (m_containerFormat) {
871 break;
872 default:
873 mediaType = MediaType::Unknown;
874 }
875 return TagParser::containerMimeType(m_containerFormat, mediaType);
876}
877
890vector<AbstractTrack *> MediaFileInfo::tracks() const
891{
892 vector<AbstractTrack *> res;
893 size_t trackCount = 0;
894 size_t containerTrackCount = 0;
895 if (m_singleTrack) {
896 trackCount = 1;
897 }
898 if (m_container) {
899 trackCount += (containerTrackCount = m_container->trackCount());
900 }
901 res.reserve(trackCount);
902
903 if (m_singleTrack) {
904 res.push_back(m_singleTrack.get());
905 }
906 for (size_t i = 0; i != containerTrackCount; ++i) {
907 res.push_back(m_container->track(i));
908 }
909 return res;
910}
911
921{
923 return false;
924 }
925 if (m_singleTrack && m_singleTrack->mediaType() == type) {
926 return true;
927 } else if (m_container) {
928 for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
929 if (m_container->track(i)->mediaType() == type) {
930 return true;
931 }
932 }
933 }
934 return false;
935}
936
946CppUtilities::TimeSpan MediaFileInfo::duration() const
947{
948 if (m_container) {
949 return m_container->duration();
950 } else if (m_singleTrack) {
951 return m_singleTrack->duration();
952 }
953 return TimeSpan();
954}
955
966{
967 const auto duration = this->duration();
968 if (duration.isNull()) {
969 return 0.0;
970 }
971 return 0.0078125 * static_cast<double>(size()) / duration.totalSeconds();
972}
973
984unordered_set<string> MediaFileInfo::availableLanguages(MediaType type) const
985{
986 unordered_set<string> res;
987 if (m_container) {
988 for (size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
989 const AbstractTrack *const track = m_container->track(i);
990 if (type != MediaType::Unknown && track->mediaType() != type) {
991 continue;
992 }
993 if (const auto &language = track->locale().someAbbreviatedName(); !language.empty()) {
994 res.emplace(language);
995 }
996 }
997 } else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type)) {
998 if (const auto &language = m_singleTrack->locale().someAbbreviatedName(); !language.empty()) {
999 res.emplace(language);
1000 }
1001 }
1002 return res;
1003}
1004
1017{
1018 if (m_container) {
1019 const size_t trackCount = m_container->trackCount();
1020 vector<string> parts;
1021 parts.reserve(trackCount);
1022 for (size_t i = 0; i != trackCount; ++i) {
1023 const string description(m_container->track(i)->description());
1024 if (!description.empty()) {
1025 parts.emplace_back(std::move(description));
1026 }
1027 }
1028 return joinStrings(parts, " / ");
1029 } else if (m_singleTrack) {
1030 return m_singleTrack->description();
1031 }
1032 return string();
1033}
1034
1045{
1047 return false;
1048 }
1049 if (m_id3v1Tag) {
1050 m_id3v1Tag.reset();
1051 return true;
1052 }
1053 return false;
1054}
1055
1072{
1074 return nullptr;
1075 }
1076 if (!m_id3v1Tag) {
1077 m_id3v1Tag = make_unique<Id3v1Tag>();
1078 }
1079 return m_id3v1Tag.get();
1080}
1081
1093{
1095 return false;
1096 }
1097 for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1098 if (i->get() == tag) {
1099 m_id3v2Tags.erase(i);
1100 return true;
1101 }
1102 }
1103 return false;
1104}
1105
1115{
1116 if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1117 return false;
1118 }
1119 m_id3v2Tags.clear();
1120 return true;
1121}
1122
1139{
1140 if (m_id3v2Tags.empty()) {
1141 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1142 }
1143 return m_id3v2Tags.front().get();
1144}
1145
1160{
1161 if (!tag) {
1162 return false;
1163 }
1164
1165 // remove tag via container
1166 if (m_container) {
1167 return m_container->removeTag(tag);
1168 }
1169
1170 // remove tag via track for "single-track" formats
1171 if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1172 auto *const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1173 if (flacStream->vorbisComment() == tag) {
1174 return flacStream->removeVorbisComment();
1175 }
1176 }
1177
1178 // remove ID3 tags
1179 if (m_id3v1Tag.get() == tag) {
1180 m_id3v1Tag.reset();
1181 return true;
1182 }
1183 for (auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1184 if (i->get() == tag) {
1185 m_id3v2Tags.erase(i);
1186 return true;
1187 }
1188 }
1189 return false;
1190}
1191
1199{
1200 if (m_container) {
1201 m_container->removeAllTags();
1202 }
1203 if (m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
1204 static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1205 }
1206 m_id3v1Tag.reset();
1207 m_id3v2Tags.clear();
1208}
1209
1214{
1215 if (m_container && m_container->chapterCount()) {
1216 return true;
1217 }
1218 switch (m_containerFormat) {
1221 return true;
1222 default:
1223 return false;
1224 }
1225}
1226
1231{
1232 if (m_container && m_container->attachmentCount()) {
1233 return true;
1234 }
1235 switch (m_containerFormat) {
1238 return true;
1239 default:
1240 return false;
1241 }
1242}
1243
1248{
1249 if (trackCount()) {
1250 return true;
1251 }
1252 switch (m_containerFormat) {
1259 return true;
1260 default:
1261 return false;
1262 }
1263}
1264
1269{
1270 switch (m_containerFormat) {
1279 // these container formats are supported
1280 return true;
1281 default:
1282 // the container format is unsupported
1283 // -> an ID3 tag might be already present, in this case the tags are considered supported
1284 return !m_container && (hasId3v1Tag() || hasId3v2Tag());
1285 }
1286}
1287
1294{
1295 // simply return the first tag here since MP4 files never contain multiple tags
1296 return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container
1297 && m_container->tagCount() > 0
1298 ? static_cast<Mp4Container *>(m_container.get())->tags().front().get()
1299 : nullptr;
1300}
1301
1308const vector<unique_ptr<MatroskaTag>> &MediaFileInfo::matroskaTags() const
1309{
1310 // matroska files might contain multiple tags (targeting different scopes)
1311 if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1312 return static_cast<MatroskaContainer *>(m_container.get())->tags();
1313 } else {
1314 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1315 return empty;
1316 }
1317}
1318
1325{
1326 return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
1327 ? static_cast<OggContainer *>(m_container.get())->tags().front().get()
1328 : (m_containerFormat == ContainerFormat::Flac && m_singleTrack ? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment() : nullptr);
1329}
1330
1336vector<AbstractChapter *> MediaFileInfo::chapters() const
1337{
1338 vector<AbstractChapter *> res;
1339 if (m_container) {
1340 const size_t count = m_container->chapterCount();
1341 res.reserve(count);
1342 for (size_t i = 0; i != count; ++i) {
1343 res.push_back(m_container->chapter(i));
1344 }
1345 }
1346 return res;
1347}
1348
1354vector<AbstractAttachment *> MediaFileInfo::attachments() const
1355{
1356 vector<AbstractAttachment *> res;
1357 if (m_container) {
1358 const size_t count = m_container->attachmentCount();
1359 res.reserve(count);
1360 for (size_t i = 0; i != count; ++i) {
1361 res.push_back(m_container->attachment(i));
1362 }
1363 }
1364 return res;
1365}
1366
1379{
1380 m_containerParsingStatus = ParsingStatus::NotParsedYet;
1381 m_containerFormat = ContainerFormat::Unknown;
1382 m_containerOffset = 0;
1383 m_paddingSize = 0;
1384 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1385 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1386 m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1387 m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1388 m_id3v1Tag.reset();
1389 m_id3v2Tags.clear();
1390 m_actualId3v2TagOffsets.clear();
1391 m_fileStructureFlags = MediaFileStructureFlags::None;
1392 m_container.reset();
1393 m_singleTrack.reset();
1394}
1395
1413{
1414 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1415 if (begin == end) {
1416 return;
1417 }
1418 Id3v2Tag &first = **begin;
1419 auto isecond = begin + 1;
1420 if (isecond == end) {
1421 return;
1422 }
1423 for (auto i = isecond; i != end; ++i) {
1424 first.insertFields(**i, false);
1425 }
1426 m_id3v2Tags.erase(isecond, end);
1427}
1428
1447
1466
1482{
1483 switch (m_containerFormat) {
1485 if (m_container) {
1486 return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1487 }
1488 break;
1490 if (m_singleTrack) {
1491 return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1492 }
1493 break;
1494 default:;
1495 }
1496 return nullptr;
1497}
1498
1509{
1510 switch (m_containerFormat) {
1512 if (m_container) {
1513 bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1514 static_cast<OggContainer *>(m_container.get())->removeAllTags();
1515 return hadTags;
1516 }
1517 break;
1519 if (m_singleTrack) {
1520 return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1521 }
1522 break;
1523 default:;
1524 }
1525 return false;
1526}
1527
1537void MediaFileInfo::tags(std::vector<Tag *> &tags) const
1538{
1539 if (hasId3v1Tag()) {
1540 tags.push_back(m_id3v1Tag.get());
1541 }
1542 for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1543 tags.push_back(tag.get());
1544 }
1545 if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1546 if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1547 tags.push_back(vorbisComment);
1548 }
1549 }
1550 if (m_container) {
1551 for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1552 tags.push_back(m_container->tag(i));
1553 }
1554 }
1555}
1556
1565vector<Tag *> MediaFileInfo::tags() const
1566{
1567 auto res = vector<Tag *>();
1568 tags(res);
1569 return res;
1570}
1571
1579{
1580 return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1581 || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1582}
1583
1593void MediaFileInfo::parsedTags(std::vector<Tag *> &tags) const
1594{
1595 if (hasId3v1Tag() && m_id3v1Tag->size()) {
1596 tags.push_back(m_id3v1Tag.get());
1597 }
1598 for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1599 if (tag->size()) {
1600 tags.push_back(tag.get());
1601 }
1602 }
1603 if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1604 if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1605 if (vorbisComment->size()) {
1606 tags.push_back(vorbisComment);
1607 }
1608 }
1609 }
1610 if (m_container) {
1611 for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1612 if (auto *const tag = m_container->tag(i); tag->size()) {
1613 tags.push_back(tag);
1614 }
1615 }
1616 }
1617}
1618
1627std::vector<Tag *> MediaFileInfo::parsedTags() const
1628{
1629 auto res = vector<Tag *>();
1630 parsedTags(res);
1631 return res;
1632}
1633
1642
1646void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1647{
1648 static const string context("making MP3/FLAC file");
1649
1650 // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1651 if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1652 && m_containerFormat != ContainerFormat::Flac) {
1653 // alter ID3v1 tag
1654 if (!m_id3v1Tag) {
1655 // remove ID3v1 tag
1656 if (!(m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag)) {
1657 diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1658 return;
1659 }
1660 progress.updateStep("Removing ID3v1 tag ...");
1661 stream().close();
1662 auto ec = std::error_code();
1663 std::filesystem::resize_file(makeNativePath(BasicFileInfo::pathForOpen(path())), size() - 128, ec);
1664 if (!ec) {
1665 reportSizeChanged(size() - 128);
1666 } else {
1667 diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag: " + ec.message(), context);
1668 throw std::ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
1669 }
1670 return;
1671 } else {
1672 // add or update ID3v1 tag
1673 if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1674 progress.updateStep("Updating existing ID3v1 tag ...");
1675 // ensure the file is still open / not readonly
1676 open();
1677 stream().seekp(-128, ios_base::end);
1678 try {
1679 m_id3v1Tag->make(stream(), diag);
1680 } catch (const Failure &) {
1681 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1682 }
1683 } else {
1684 progress.updateStep("Adding new ID3v1 tag ...");
1685 // ensure the file is still open / not readonly
1686 open();
1687 stream().seekp(0, ios_base::end);
1688 try {
1689 m_id3v1Tag->make(stream(), diag);
1690 } catch (const Failure &) {
1691 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1692 }
1693 }
1694 // prevent deferring final write operations (to catch and handle possible errors here)
1695 stream().flush();
1696 }
1697 return;
1698 }
1699
1700 // ID3v2 needs to be modified
1701 FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1702 progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1703
1704 // prepare ID3v2 tags
1705 vector<Id3v2TagMaker> makers;
1706 makers.reserve(m_id3v2Tags.size());
1707 std::uint64_t tagsSize = 0;
1708 for (auto &tag : m_id3v2Tags) {
1709 try {
1710 makers.emplace_back(tag->prepareMaking(diag));
1711 tagsSize += makers.back().requiredSize();
1712 } catch (const Failure &) {
1713 }
1714 }
1715
1716 // determine stream offset and make track/format specific metadata
1717 std::uint32_t streamOffset; // where the actual stream starts
1718 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1719 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1720 std::streamoff startOfLastMetaDataBlock;
1721 if (flacStream) {
1722 // if it is a raw FLAC stream, make FLAC metadata
1723 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1724 tagsSize += static_cast<std::uint64_t>(flacMetaData.tellp());
1725 streamOffset = flacStream->streamOffset();
1726 } else {
1727 // make no further metadata, just use the container offset as stream offset
1728 streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1729 }
1730
1731 // check whether rewrite is required
1732 bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1733 size_t padding = 0;
1734 if (!rewriteRequired) {
1735 // rewriting is not forced and new tag is not too big for available space
1736 // -> calculate new padding
1737 padding = streamOffset - tagsSize;
1738 // -> check whether the new padding matches specifications
1739 if (padding < minPadding() || padding > maxPadding()) {
1740 rewriteRequired = true;
1741 }
1742 }
1743 if (makers.empty() && !flacStream) {
1744 // an ID3v2 tag is not written and it is not a FLAC stream
1745 // -> can't include padding
1746 if (padding) {
1747 // but padding would be present -> need to rewrite
1748 padding = 0; // can't write the preferred padding despite rewriting
1749 rewriteRequired = true;
1750 }
1751 } else if (rewriteRequired) {
1752 // rewriting is forced or new ID3v2 tag is too big for available space
1753 // -> use preferred padding when rewriting anyways
1754 padding = preferredPadding();
1755 } else if (makers.empty() && flacStream && padding && padding < 4) {
1756 // no ID3v2 tag -> must include padding in FLAC stream
1757 // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1758 padding = preferredPadding();
1759 rewriteRequired = true;
1760 }
1761 if (rewriteRequired && flacStream && makers.empty() && padding) {
1762 // the first 4 byte of FLAC padding actually don't count because these
1763 // can not be used for additional meta data
1764 padding += 4;
1765 }
1766 progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1767
1768 // setup stream(s) for writing
1769 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1770 string originalPath = path(), backupPath;
1771 NativeFileStream &outputStream = stream();
1772 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1773
1774 if (rewriteRequired) {
1775 if (m_saveFilePath.empty()) {
1776 // move current file to temp dir and reopen it as backupStream, recreate original file
1777 try {
1778 BackupHelper::createBackupFileCanonical(backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1779 // recreate original file, define buffer variables
1780 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1781 } catch (const std::ios_base::failure &failure) {
1782 diag.emplace_back(
1783 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1784 throw;
1785 }
1786 } else {
1787 // open the current file as backupStream and create a new outputStream at the specified "save file path"
1788 try {
1789 close();
1790 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1791 backupStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::binary);
1792 outputStream.open(BasicFileInfo::pathForOpen(m_saveFilePath).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1793 } catch (const std::ios_base::failure &failure) {
1794 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1795 throw;
1796 }
1797 }
1798
1799 } else { // !rewriteRequired
1800 // reopen original file to ensure it is opened for writing
1801 try {
1802 close();
1803 outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::out | ios_base::binary);
1804 } catch (const std::ios_base::failure &failure) {
1805 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1806 throw;
1807 }
1808 }
1809 // TODO: fix code duplication
1810
1811 // start actual writing
1812 try {
1813 // ensure we can cast padding safely to uint32
1814 if (padding > numeric_limits<std::uint32_t>::max()) {
1815 padding = numeric_limits<std::uint32_t>::max();
1816 diag.emplace_back(
1817 DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1818 }
1819
1820 if (!makers.empty()) {
1821 // write ID3v2 tags
1822 progress.updateStep("Writing ID3v2 tag ...");
1823 for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1824 i->make(outputStream, 0, diag);
1825 }
1826 // include padding into the last ID3v2 tag
1827 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1828 }
1829
1830 if (flacStream) {
1831 if (padding && startOfLastMetaDataBlock) {
1832 // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1833 flacMetaData.seekg(startOfLastMetaDataBlock);
1834 flacMetaData.seekp(startOfLastMetaDataBlock);
1835 flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1836 flacMetaData.seekg(0);
1837 }
1838
1839 // write FLAC metadata
1840 outputStream << flacMetaData.rdbuf();
1841
1842 // write padding
1843 if (padding) {
1844 flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding), true, diag);
1845 }
1846 }
1847
1848 if (makers.empty() && !flacStream) {
1849 // just write padding (however, padding should be set to 0 in this case?)
1850 for (; padding; --padding) {
1851 outputStream.put(0);
1852 }
1853 }
1854
1855 // copy / skip actual stream data
1856 // -> determine media data size
1857 std::uint64_t mediaDataSize = size() - streamOffset;
1858 if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1859 mediaDataSize -= 128;
1860 }
1861
1862 if (rewriteRequired) {
1863 // copy data from original file
1864 switch (m_containerFormat) {
1866 progress.updateStep("Writing MPEG audio frames ...");
1867 break;
1868 default:
1869 progress.updateStep("Writing data ...");
1870 }
1871 backupStream.seekg(static_cast<streamoff>(streamOffset));
1872 CopyHelper<0x4000> copyHelper;
1873 copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, std::bind(&AbortableProgressFeedback::isAborted, std::ref(progress)),
1874 std::bind(&AbortableProgressFeedback::updateStepPercentage, std::ref(progress), _1));
1875 } else {
1876 // just skip actual stream data
1877 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1878 }
1879
1880 // write ID3v1 tag
1881 if (m_id3v1Tag) {
1882 progress.updateStep("Writing ID3v1 tag ...");
1883 try {
1884 m_id3v1Tag->make(stream(), diag);
1885 } catch (const Failure &) {
1886 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1887 }
1888 }
1889
1890 // handle streams
1891 if (rewriteRequired) {
1892 // report new size
1893 reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1894 // "save as path" is now the regular path
1895 if (!saveFilePath().empty()) {
1897 m_saveFilePath.clear();
1898 }
1899 // prevent deferring final write operations (to catch and handle possible errors here); stream is useless for further
1900 // usage anyways because it is write-only
1901 outputStream.close();
1902 } else {
1903 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1904 if (newSize < size()) {
1905 // file is smaller after the modification -> truncate
1906 // -> prevent deferring final write operations
1907 outputStream.close();
1908 // -> truncate file
1909 auto ec = std::error_code();
1910 std::filesystem::resize_file(makeNativePath(BasicFileInfo::pathForOpen(path())), newSize, ec);
1911 if (!ec) {
1912 reportSizeChanged(newSize);
1913 } else {
1914 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file: " + ec.message(), context);
1915 }
1916 } else {
1917 // file is longer after the modification
1918 // -> prevent deferring final write operations (to catch and handle possible errors here)
1919 outputStream.flush();
1920 // -> report new size
1921 reportSizeChanged(newSize);
1922 }
1923 }
1924
1925 } catch (...) {
1926 BackupHelper::handleFailureAfterFileModifiedCanonical(*this, originalPath, backupPath, outputStream, backupStream, diag, context);
1927 }
1928}
1929
1930} // 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,...
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.
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.
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
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...
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::uint64_t effectiveSize() const
Returns the "effective size" of the file if know; otherwise returns 0.
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>.
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.
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:166
std::uint64_t size() const
Returns the size the tag within the file it is parsed from in bytes.
Definition tag.h:234
virtual std::size_t insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition tag.cpp:89
Implementation of TagParser::Tag for Vorbis comments.
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...
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize)
Definition signature.h:83
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.
ParsingStatus
The ParsingStatus enum specifies whether a certain part of the file (tracks, tags,...
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.
ContainerFormat
Specifies the container format.
Definition signature.h:18
MediaFileHandlingFlags
The MediaFileHandlingFlags enum specifies flags which controls the behavior of MediaFileInfo objects.
const LocaleDetail & someAbbreviatedName(LocaleFormat preferredFormat=LocaleFormat::BCP_47) const
Returns some abbreviated name, preferably of the specified preferredFormat.
The MediaFileInfoPrivate struct contains private fields of the MediaFileInfo class.
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