Tag Parser: mediafileinfo.cpp Source File

Tag Parser 12.5.3 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"
7#include "./progressfeedback.h"
8#include "./signature.h"
9#include "./tag.h"
10
11#include "./id3/id3v1tag.h"
12#include "./id3/id3v2tag.h"
13
14#include "./wav/waveaudiostream.h"
15
16#include "./mpegaudio/mpegaudioframestream.h"
17
18#include "./adts/adtsstream.h"
19
20#include "./ivf/ivfstream.h"
21
22#include "./mp4/mp4atom.h"
23#include "./mp4/mp4container.h"
24#include "./mp4/mp4ids.h"
25#include "./mp4/mp4tag.h"
26#include "./mp4/mp4track.h"
27
28#include "./matroska/ebmlelement.h"
29#include "./matroska/matroskacontainer.h"
30#include "./matroska/matroskatag.h"
31#include "./matroska/matroskatrack.h"
32
33#include "./ogg/oggcontainer.h"
34
35#include "./flac/flacmetadata.h"
36#include "./flac/flacstream.h"
37
38#include <c++utilities/chrono/timespan.h>
39#include <c++utilities/conversion/stringconversion.h>
40#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
60
61namespace TagParser {
62
64 struct MediaFileInfoPrivate {};
65
75
81 MediaFileInfo::MediaFileInfo(std::string &&path)
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)
98 , m_fileHandlingFlags(MediaFileHandlingFlags::ForceRewrite | MediaFileHandlingFlags::ForceTagPosition | MediaFileHandlingFlags::ForceIndexPosition
99 | MediaFileHandlingFlags::NormalizeKnownTagFieldIds | MediaFileHandlingFlags::PreserveRawTimingValues)
100 , m_maxFullParseSize(0x3200000)
101{
102}
103
107MediaFileInfo::MediaFileInfo()
108 : MediaFileInfo(std::string())
109{
110}
111
115 MediaFileInfo::MediaFileInfo(std::string_view path)
116 : MediaFileInfo(std::string(path))
117{
118}
119
123MediaFileInfo::~MediaFileInfo()
124{
125}
126
140 void MediaFileInfo::parseContainerFormat(Diagnostics &diag, AbortableProgressFeedback &progress)
141{
142 CPP_UTILITIES_UNUSED(progress)
143
144 // skip if container format already parsed
145 if (containerParsingStatus() != ParsingStatus::NotParsedYet) {
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)))) {
198 case ContainerFormat::Id3v2Tag:
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
220 case ContainerFormat::Mp4:
221 case ContainerFormat::QuickTime: {
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 }
233 case ContainerFormat::Ebml: {
234 // EBML/Matroska is handled using MatroskaContainer instance
235 auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
236 try {
237 container->parseHeader(diag, 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 }
257 case ContainerFormat::Ogg:
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;
262 case ContainerFormat::Unknown:
263 case ContainerFormat::ApeTag:
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
324 void MediaFileInfo::parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
325{
326 // skip if tracks already parsed
327 if (tracksParsingStatus() != ParsingStatus::NotParsedYet) {
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) {
342 case ContainerFormat::Adts:
343 m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
344 break;
345 case ContainerFormat::Flac:
346 m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
347 break;
348 case ContainerFormat::Ivf:
349 m_singleTrack = make_unique<IvfStream>(stream(), m_containerOffset);
350 break;
351 case ContainerFormat::MpegAudioFrames:
352 m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
353 break;
354 case ContainerFormat::RiffWave:
355 m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
356 break;
357 default:
358 throw NotImplementedException();
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) {
371 case ContainerFormat::Flac:
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
402 void MediaFileInfo::parseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
403{
404 // skip if tags already parsed
405 if (tagsParsingStatus() != ParsingStatus::NotParsedYet) {
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);
417 m_fileStructureFlags += MediaFileStructureFlags::ActualExistingId3v1Tag;
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) {
489 throw NotImplementedException();
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
521 void MediaFileInfo::parseChapters(Diagnostics &diag, AbortableProgressFeedback &progress)
522{
523 // skip if chapters already parsed
524 if (chaptersParsingStatus() != ParsingStatus::NotParsedYet) {
525 return;
526 }
527 static const string context("parsing chapters");
528
529 try {
530 // parse chapters via container object
531 if (!m_container) {
532 throw NotImplementedException();
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
554 void MediaFileInfo::parseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress)
555{
556 // skip if attachments already parsed
557 if (attachmentsParsingStatus() != ParsingStatus::NotParsedYet) {
558 return;
559 }
560 static const string context("parsing attachments");
561
562 try {
563 // parse attachments via container object
564 if (!m_container) {
565 throw NotImplementedException();
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
584 void MediaFileInfo::parseEverything(Diagnostics &diag, AbortableProgressFeedback &progress)
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
616 bool MediaFileInfo::createAppropriateTags(const TagCreationSettings &settings)
617{
618 // check if tags have been parsed yet (tags must have been parsed yet to create appropriate tags)
619 if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
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) {
656 case ContainerFormat::Flac:
657 static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
658 break;
659 default:
660 // create ID3 tag(s)
661 if (!hasAnyTag() && !(flags & TagCreationFlags::TreatUnknownFilesAsMp3Files)) {
662 switch (containerFormat()) {
663 case ContainerFormat::Adts:
664 case ContainerFormat::Aiff:
665 case ContainerFormat::MpegAudioFrames:
666 case ContainerFormat::WavPack:
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();
676 if (flags & TagCreationFlags::Id3InitOnCreate) {
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
681 id3v1Tag->ensureTextValuesAreProperlyEncoded();
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
695 if (flags & TagCreationFlags::MergeMultipleSuccessiveId3v2Tags) {
696 mergeId3v2Tags();
697 }
698 // remove ID3 tags according to settings
699 if (settings.id3v1usage == TagUsage::Never && hasId3v1Tag()) {
700 // transfer tags to ID3v2 tag before removing
701 if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) && hasId3v2Tag()) {
702 id3v2Tags().front()->insertValues(*id3v1Tag(), false);
703 }
704 removeId3v1Tag();
705 }
706 if (settings.id3v2usage == TagUsage::Never) {
707 if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) && hasId3v1Tag()) {
708 // transfer tags to ID3v1 tag before removing
709 for (const auto &tag : id3v2Tags()) {
710 id3v1Tag()->insertValues(*tag, false);
711 }
712 }
713 removeAllId3v2Tags();
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
743 void MediaFileInfo::applyChanges(Diagnostics &diag, AbortableProgressFeedback &progress)
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()) {
749 case ParsingStatus::Ok:
750 case ParsingStatus::NotSupported:
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()) {
757 case ParsingStatus::Ok:
758 case ParsingStatus::NotSupported:
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
781 clearParsingResults();
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
790 clearParsingResults();
791 throw;
792 }
793 }
794 clearParsingResults();
795}
796
809string_view MediaFileInfo::containerFormatAbbreviation() const
810{
811 MediaType mediaType = MediaType::Unknown;
812 unsigned int version = 0;
813 switch (m_containerFormat) {
814 case ContainerFormat::Ogg: {
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 }
839 case ContainerFormat::Matroska:
840 case ContainerFormat::Mp4:
841 mediaType = hasTracksOfType(MediaType::Video) ? MediaType::Video : MediaType::Audio;
842 break;
843 case ContainerFormat::MpegAudioFrames:
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) {
867 case ContainerFormat::Mp4:
868 case ContainerFormat::Ogg:
869 case ContainerFormat::Matroska:
870 mediaType = hasTracksOfType(MediaType::Video) ? MediaType::Video : MediaType::Audio;
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
920 bool MediaFileInfo::hasTracksOfType(MediaType type) const
921{
922 if (tracksParsingStatus() == ParsingStatus::NotParsedYet) {
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
965 double MediaFileInfo::overallAverageBitrate() const
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
1016 string MediaFileInfo::technicalSummary() const
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
1044 bool MediaFileInfo::removeId3v1Tag()
1045{
1046 if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
1047 return false;
1048 }
1049 if (m_id3v1Tag) {
1050 m_id3v1Tag.reset();
1051 return true;
1052 }
1053 return false;
1054}
1055
1071 Id3v1Tag *MediaFileInfo::createId3v1Tag()
1072{
1073 if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
1074 return nullptr;
1075 }
1076 if (!m_id3v1Tag) {
1077 m_id3v1Tag = make_unique<Id3v1Tag>();
1078 }
1079 return m_id3v1Tag.get();
1080}
1081
1092 bool MediaFileInfo::removeId3v2Tag(Id3v2Tag *tag)
1093{
1094 if (tagsParsingStatus() == ParsingStatus::NotParsedYet) {
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
1114 bool MediaFileInfo::removeAllId3v2Tags()
1115{
1116 if (tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1117 return false;
1118 }
1119 m_id3v2Tags.clear();
1120 return true;
1121}
1122
1138 Id3v2Tag *MediaFileInfo::createId3v2Tag()
1139{
1140 if (m_id3v2Tags.empty()) {
1141 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1142 }
1143 return m_id3v2Tags.front().get();
1144}
1145
1159 bool MediaFileInfo::removeTag(Tag *tag)
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
1198 void MediaFileInfo::removeAllTags()
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
1213 bool MediaFileInfo::areChaptersSupported() const
1214{
1215 if (m_container && m_container->chapterCount()) {
1216 return true;
1217 }
1218 switch (m_containerFormat) {
1219 case ContainerFormat::Matroska:
1220 case ContainerFormat::Webm:
1221 return true;
1222 default:
1223 return false;
1224 }
1225}
1226
1230 bool MediaFileInfo::areAttachmentsSupported() const
1231{
1232 if (m_container && m_container->attachmentCount()) {
1233 return true;
1234 }
1235 switch (m_containerFormat) {
1236 case ContainerFormat::Matroska:
1237 case ContainerFormat::Webm:
1238 return true;
1239 default:
1240 return false;
1241 }
1242}
1243
1247 bool MediaFileInfo::areTracksSupported() const
1248{
1249 if (trackCount()) {
1250 return true;
1251 }
1252 switch (m_containerFormat) {
1253 case ContainerFormat::Mp4:
1254 case ContainerFormat::MpegAudioFrames:
1255 case ContainerFormat::RiffWave:
1256 case ContainerFormat::Ogg:
1257 case ContainerFormat::Matroska:
1258 case ContainerFormat::Webm:
1259 return true;
1260 default:
1261 return false;
1262 }
1263}
1264
1268 bool MediaFileInfo::areTagsSupported() const
1269{
1270 switch (m_containerFormat) {
1271 case ContainerFormat::Adts:
1272 case ContainerFormat::Flac:
1273 case ContainerFormat::Matroska:
1274 case ContainerFormat::MpegAudioFrames:
1275 case ContainerFormat::Mp4:
1276 case ContainerFormat::Ogg:
1277 case ContainerFormat::WavPack:
1278 case ContainerFormat::Webm:
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
1293 Mp4Tag *MediaFileInfo::mp4Tag() const
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
1308 const 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
1324 VorbisComment *MediaFileInfo::vorbisComment() const
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
1378 void MediaFileInfo::clearParsingResults()
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
1399 void MediaFileInfo::writePadding(std::ostream &outputStream, std::uint64_t size)
1400{
1401 static constexpr auto bufferSize = static_cast<std::uint64_t>(1024 * 8);
1402 for (constexpr char buffer[bufferSize] = {}; size;) {
1403 const auto count = std::min(bufferSize, size);
1404 outputStream.write(buffer, static_cast<std::streamsize>(count));
1405 size -= count;
1406 }
1407}
1408
1425 void MediaFileInfo::mergeId3v2Tags()
1426{
1427 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1428 if (begin == end) {
1429 return;
1430 }
1431 Id3v2Tag &first = **begin;
1432 auto isecond = begin + 1;
1433 if (isecond == end) {
1434 return;
1435 }
1436 for (auto i = isecond; i != end; ++i) {
1437 first.insertFields(**i, false);
1438 }
1439 m_id3v2Tags.erase(isecond, end);
1440}
1441
1452 bool MediaFileInfo::id3v1ToId3v2()
1453{
1454 if (tagsParsingStatus() == ParsingStatus::NotParsedYet || !areTagsSupported() || !hasId3v1Tag()) {
1455 return false;
1456 }
1457 return createAppropriateTags(TagCreationSettings{
1458 {}, TagCreationFlags::MergeMultipleSuccessiveId3v2Tags | TagCreationFlags::KeepExistingId3v2Version, TagUsage::Never, TagUsage::Always, 3 });
1459}
1460
1471 bool MediaFileInfo::id3v2ToId3v1()
1472{
1473 if (tagsParsingStatus() == ParsingStatus::NotParsedYet || !areTagsSupported() || !hasId3v2Tag()) {
1474 return false;
1475 }
1476 return createAppropriateTags(TagCreationSettings{
1477 {}, TagCreationFlags::MergeMultipleSuccessiveId3v2Tags | TagCreationFlags::KeepExistingId3v2Version, TagUsage::Always, TagUsage::Never, 3 });
1478}
1479
1494 VorbisComment *MediaFileInfo::createVorbisComment()
1495{
1496 switch (m_containerFormat) {
1497 case ContainerFormat::Ogg:
1498 if (m_container) {
1499 return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
1500 }
1501 break;
1502 case ContainerFormat::Flac:
1503 if (m_singleTrack) {
1504 return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
1505 }
1506 break;
1507 default:;
1508 }
1509 return nullptr;
1510}
1511
1521 bool MediaFileInfo::removeVorbisComment()
1522{
1523 switch (m_containerFormat) {
1524 case ContainerFormat::Ogg:
1525 if (m_container) {
1526 bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1527 static_cast<OggContainer *>(m_container.get())->removeAllTags();
1528 return hadTags;
1529 }
1530 break;
1531 case ContainerFormat::Flac:
1532 if (m_singleTrack) {
1533 return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
1534 }
1535 break;
1536 default:;
1537 }
1538 return false;
1539}
1540
1550 void MediaFileInfo::tags(std::vector<Tag *> &tags) const
1551{
1552 if (hasId3v1Tag()) {
1553 tags.push_back(m_id3v1Tag.get());
1554 }
1555 for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1556 tags.push_back(tag.get());
1557 }
1558 if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1559 if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1560 tags.push_back(vorbisComment);
1561 }
1562 }
1563 if (m_container) {
1564 for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1565 tags.push_back(m_container->tag(i));
1566 }
1567 }
1568}
1569
1578vector<Tag *> MediaFileInfo::tags() const
1579{
1580 auto res = vector<Tag *>();
1581 tags(res);
1582 return res;
1583}
1584
1591 bool MediaFileInfo::hasAnyTag() const
1592{
1593 return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount())
1594 || (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
1595}
1596
1606 void MediaFileInfo::parsedTags(std::vector<Tag *> &tags) const
1607{
1608 if (hasId3v1Tag() && m_id3v1Tag->size()) {
1609 tags.push_back(m_id3v1Tag.get());
1610 }
1611 for (const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1612 if (tag->size()) {
1613 tags.push_back(tag.get());
1614 }
1615 }
1616 if (m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
1617 if (auto *const vorbisComment = static_cast<const FlacStream *>(m_singleTrack.get())->vorbisComment()) {
1618 if (vorbisComment->size()) {
1619 tags.push_back(vorbisComment);
1620 }
1621 }
1622 }
1623 if (m_container) {
1624 for (size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1625 if (auto *const tag = m_container->tag(i); tag->size()) {
1626 tags.push_back(tag);
1627 }
1628 }
1629 }
1630}
1631
1640std::vector<Tag *> MediaFileInfo::parsedTags() const
1641{
1642 auto res = vector<Tag *>();
1643 parsedTags(res);
1644 return res;
1645}
1646
1650 void MediaFileInfo::invalidated()
1651{
1652 BasicFileInfo::invalidated();
1653 clearParsingResults();
1654}
1655
1659void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &progress)
1660{
1661 static const string context("making MP3/FLAC file");
1662
1663 // don't rewrite the complete file if there are no ID3v2/FLAC tags present or to be written
1664 if (!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1665 && m_containerFormat != ContainerFormat::Flac) {
1666 // alter ID3v1 tag
1667 if (!m_id3v1Tag) {
1668 // remove ID3v1 tag
1669 if (!(m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag)) {
1670 diag.emplace_back(DiagLevel::Information, "Nothing to be changed.", context);
1671 return;
1672 }
1673 progress.updateStep("Removing ID3v1 tag ...");
1674 stream().close();
1675 auto ec = std::error_code();
1676 std::filesystem::resize_file(makeNativePath(BasicFileInfo::pathForOpen(path())), size() - 128, ec);
1677 if (!ec) {
1678 reportSizeChanged(size() - 128);
1679 } else {
1680 diag.emplace_back(DiagLevel::Critical, "Unable to truncate file to remove ID3v1 tag: " + ec.message(), context);
1681 throw std::ios_base::failure("Unable to truncate file to remove ID3v1 tag.");
1682 }
1683 return;
1684 } else {
1685 // add or update ID3v1 tag
1686 if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1687 progress.updateStep("Updating existing ID3v1 tag ...");
1688 // ensure the file is still open / not readonly
1689 open();
1690 stream().seekp(-128, ios_base::end);
1691 try {
1692 m_id3v1Tag->make(stream(), diag);
1693 } catch (const Failure &) {
1694 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1695 }
1696 } else {
1697 progress.updateStep("Adding new ID3v1 tag ...");
1698 // ensure the file is still open / not readonly
1699 open();
1700 stream().seekp(0, ios_base::end);
1701 try {
1702 m_id3v1Tag->make(stream(), diag);
1703 } catch (const Failure &) {
1704 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1705 }
1706 }
1707 // prevent deferring final write operations (to catch and handle possible errors here)
1708 stream().flush();
1709 }
1710 return;
1711 }
1712
1713 // ID3v2 needs to be modified
1714 FlacStream *const flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
1715 progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
1716
1717 // prepare ID3v2 tags
1718 auto makers = std::vector<Id3v2TagMaker>();
1719 makers.reserve(m_id3v2Tags.size());
1720 auto tagsSize = std::uint64_t();
1721 for (auto &tag : m_id3v2Tags) {
1722 try {
1723 makers.emplace_back(tag->prepareMaking(diag));
1724 tagsSize += makers.back().requiredSize();
1725 } catch (const Failure &) {
1726 }
1727 }
1728
1729 // determine stream offset and make track/format specific metadata
1730 auto streamOffset = std::uint32_t(); // where the actual stream starts
1731 auto flacMetaData = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
1732 flacMetaData.exceptions(std::ios_base::badbit | std::ios_base::failbit);
1733 auto startOfLastMetaDataBlock = std::streamoff();
1734 if (flacStream) {
1735 // if it is a raw FLAC stream, make FLAC metadata
1736 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1737 tagsSize += static_cast<std::uint64_t>(flacMetaData.tellp());
1738 streamOffset = flacStream->streamOffset();
1739 } else {
1740 // make no further metadata, just use the container offset as stream offset
1741 streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1742 }
1743
1744 // check whether rewrite is required
1745 auto rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1746 auto padding = std::size_t();
1747 if (!rewriteRequired) {
1748 // rewriting is not forced and new tag is not too big for available space
1749 // -> calculate new padding
1750 padding = streamOffset - tagsSize;
1751 // -> check whether the new padding matches specifications
1752 if (padding < minPadding() || padding > maxPadding()) {
1753 rewriteRequired = true;
1754 }
1755 }
1756 if (makers.empty() && !flacStream) {
1757 // an ID3v2 tag is not written and it is not a FLAC stream
1758 // -> can't include padding
1759 if (padding) {
1760 // but padding would be present -> need to rewrite
1761 padding = 0; // can't write the preferred padding despite rewriting
1762 rewriteRequired = true;
1763 }
1764 } else if (rewriteRequired) {
1765 // rewriting is forced or new ID3v2 tag is too big for available space
1766 // -> use preferred padding when rewriting anyways
1767 padding = preferredPadding();
1768 } else if (makers.empty() && flacStream && padding && padding < 4) {
1769 // no ID3v2 tag -> must include padding in FLAC stream
1770 // but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
1771 padding = preferredPadding();
1772 rewriteRequired = true;
1773 }
1774 if (rewriteRequired && flacStream && makers.empty() && padding) {
1775 // the first 4 byte of FLAC padding actually don't count because these
1776 // can not be used for additional meta data
1777 padding += 4;
1778 }
1779 progress.updateStep(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
1780
1781 // setup stream(s) for writing
1782 // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1783 string originalPath = path(), backupPath;
1784 NativeFileStream &outputStream = stream();
1785 NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1786
1787 if (rewriteRequired) {
1788 if (m_saveFilePath.empty()) {
1789 // move current file to temp dir and reopen it as backupStream, recreate original file
1790 try {
1791 BackupHelper::createBackupFileCanonical(backupDirectory(), originalPath, backupPath, outputStream, backupStream);
1792 // recreate original file, define buffer variables
1793 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1794 } catch (const std::ios_base::failure &failure) {
1795 diag.emplace_back(
1796 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1797 throw;
1798 }
1799 } else {
1800 // open the current file as backupStream and create a new outputStream at the specified "save file path"
1801 try {
1802 close();
1803 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1804 backupStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::binary);
1805 outputStream.open(BasicFileInfo::pathForOpen(m_saveFilePath).data(), ios_base::out | ios_base::binary | ios_base::trunc);
1806 } catch (const std::ios_base::failure &failure) {
1807 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
1808 throw;
1809 }
1810 }
1811
1812 } else { // !rewriteRequired
1813 // reopen original file to ensure it is opened for writing
1814 try {
1815 close();
1816 outputStream.open(BasicFileInfo::pathForOpen(path()).data(), ios_base::in | ios_base::out | ios_base::binary);
1817 } catch (const std::ios_base::failure &failure) {
1818 diag.emplace_back(DiagLevel::Critical, argsToString("Opening the file with write permissions failed: ", failure.what()), context);
1819 throw;
1820 }
1821 }
1822 // TODO: fix code duplication
1823
1824 // start actual writing
1825 try {
1826 // ensure we can cast padding safely to uint32
1827 if (padding > numeric_limits<std::uint32_t>::max()) {
1828 padding = numeric_limits<std::uint32_t>::max();
1829 diag.emplace_back(
1830 DiagLevel::Critical, argsToString("Preferred padding is not supported. Setting preferred padding to ", padding, '.'), context);
1831 }
1832
1833 if (!makers.empty()) {
1834 // write ID3v2 tags
1835 progress.updateStep("Writing ID3v2 tag ...");
1836 for (auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1837 i->make(outputStream, 0, diag);
1838 }
1839 // include padding into the last ID3v2 tag
1840 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1841 }
1842
1843 if (flacStream) {
1844 if (padding && startOfLastMetaDataBlock) {
1845 // if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
1846 flacMetaData.seekg(startOfLastMetaDataBlock);
1847 flacMetaData.seekp(startOfLastMetaDataBlock);
1848 flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1849 flacMetaData.seekg(0);
1850 }
1851
1852 // write FLAC metadata
1853 outputStream << flacMetaData.rdbuf();
1854
1855 // write padding
1856 if (padding) {
1857 flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding), true, diag);
1858 }
1859 }
1860
1861 if (makers.empty() && !flacStream) {
1862 writePadding(outputStream, padding);
1863 }
1864
1865 // copy / skip actual stream data
1866 // -> determine media data size
1867 std::uint64_t mediaDataSize = size() - streamOffset;
1868 if (m_fileStructureFlags & MediaFileStructureFlags::ActualExistingId3v1Tag) {
1869 mediaDataSize -= 128;
1870 }
1871
1872 if (rewriteRequired) {
1873 // copy data from original file
1874 switch (m_containerFormat) {
1875 case ContainerFormat::MpegAudioFrames:
1876 progress.updateStep("Writing MPEG audio frames ...");
1877 break;
1878 default:
1879 progress.updateStep("Writing data ...");
1880 }
1881 backupStream.seekg(static_cast<streamoff>(streamOffset));
1882 CopyHelper<0x4000> copyHelper;
1883 copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, std::bind(&AbortableProgressFeedback::isAborted, std::ref(progress)),
1884 std::bind(&AbortableProgressFeedback::updateStepPercentage, std::ref(progress), _1));
1885 } else {
1886 // just skip actual stream data
1887 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1888 }
1889
1890 // write ID3v1 tag
1891 if (m_id3v1Tag) {
1892 progress.updateStep("Writing ID3v1 tag ...");
1893 try {
1894 m_id3v1Tag->make(stream(), diag);
1895 } catch (const Failure &) {
1896 diag.emplace_back(DiagLevel::Warning, "Unable to write ID3v1 tag.", context);
1897 }
1898 }
1899
1900 // handle streams
1901 if (rewriteRequired) {
1902 // report new size
1903 reportSizeChanged(static_cast<std::uint64_t>(outputStream.tellp()));
1904 // "save as path" is now the regular path
1905 if (!saveFilePath().empty()) {
1906 reportPathChanged(saveFilePath());
1907 m_saveFilePath.clear();
1908 }
1909 // prevent deferring final write operations (to catch and handle possible errors here); stream is useless for further
1910 // usage anyways because it is write-only
1911 outputStream.close();
1912 } else {
1913 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1914 if (newSize < size()) {
1915 // file is smaller after the modification -> truncate
1916 // -> prevent deferring final write operations
1917 outputStream.close();
1918 // -> truncate file
1919 auto ec = std::error_code();
1920 std::filesystem::resize_file(makeNativePath(BasicFileInfo::pathForOpen(path())), newSize, ec);
1921 if (!ec) {
1922 reportSizeChanged(newSize);
1923 } else {
1924 diag.emplace_back(DiagLevel::Critical, "Unable to truncate the file: " + ec.message(), context);
1925 }
1926 } else {
1927 // file is longer after the modification
1928 // -> prevent deferring final write operations (to catch and handle possible errors here)
1929 outputStream.flush();
1930 // -> report new size
1931 reportSizeChanged(newSize);
1932 }
1933 }
1934
1935 } catch (...) {
1936 BackupHelper::handleFailureAfterFileModifiedCanonical(*this, originalPath, backupPath, outputStream, backupStream, diag, context);
1937 }
1938}
1939
1940} // namespace TagParser
abstracttrack.h
adtsstream.h
backuphelper.h
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition progressfeedback.h:186
TagParser::AbortableProgressFeedback::isAborted
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
Definition progressfeedback.h:236
TagParser::AbstractTrack
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition abstracttrack.h:110
TagParser::AbstractTrack::locale
const Locale & locale() const
Returns the locale of the track if known; otherwise returns an empty locale.
Definition abstracttrack.h:508
TagParser::AbstractTrack::mediaType
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
Definition abstracttrack.h:378
TagParser::BasicFileInfo::BasicFileInfo
BasicFileInfo()
Constructs a new BasicFileInfo for the specified file.
Definition basicfileinfo.cpp:23
TagParser::BasicFileInfo::reportPathChanged
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
Definition basicfileinfo.h:133
TagParser::BasicFileInfo::path
const std::string & path() const
Returns the path of the current file.
Definition basicfileinfo.h:103
TagParser::BasicFileInfo::size
std::uint64_t size() const
Returns size of the current file in bytes.
Definition basicfileinfo.h:115
TagParser::BasicFileInfo::invalidated
virtual void invalidated()
This function is called when the BasicFileInfo gets invalidated.
Definition basicfileinfo.cpp:259
TagParser::BasicFileInfo::stream
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition basicfileinfo.h:85
TagParser::BasicFileInfo::open
void open(bool readOnly=false)
Opens a std::fstream for the current file.
Definition basicfileinfo.cpp:71
TagParser::BasicFileInfo::close
void close()
A possibly opened std::fstream will be closed.
Definition basicfileinfo.cpp:97
TagParser::BasicFileInfo::pathForOpen
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(),...
Definition basicfileinfo.h:153
TagParser::BasicFileInfo::reportSizeChanged
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
Definition basicfileinfo.h:124
TagParser::BasicProgressFeedback::updateStep
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
Definition progressfeedback.h:96
TagParser::BasicProgressFeedback< AbortableProgressFeedback >::updateStepPercentage
void updateStepPercentage(std::uint8_t stepPercentage)
Definition progressfeedback.h:124
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::FieldMapBasedTag::insertFields
std::size_t insertFields(const FieldMapBasedTag< ImplementationType > &from, bool overwrite)
Inserts all fields from another tag of the same field type and compare function.
Definition fieldbasedtag.h:401
TagParser::FlacStream
Implementation of TagParser::AbstractTrack for raw FLAC streams.
Definition flacstream.h:14
TagParser::FlacStream::createVorbisComment
VorbisComment * createVorbisComment()
Creates a new Vorbis comment for the stream.
Definition flacstream.cpp:44
TagParser::FlacStream::paddingSize
std::uint32_t paddingSize() const
Returns the padding size.
Definition flacstream.h:59
TagParser::FlacStream::removeVorbisComment
bool removeVorbisComment()
Removes the assigned Vorbis comment if one is assigned; does nothing otherwise.
Definition flacstream.cpp:56
TagParser::FlacStream::vorbisComment
VorbisComment * vorbisComment() const
Returns the Vorbis comment if one is present in the stream.
Definition flacstream.h:51
TagParser::GenericContainer::tracks
const std::vector< std::unique_ptr< TrackType > > & tracks() const
Returns the tracks of the file.
Definition genericcontainer.h:238
TagParser::GenericContainer::validateElementStructure
void validateElementStructure(Diagnostics &diag, AbortableProgressFeedback &progress, std::uint64_t *paddingSize=nullptr)
Parses all elements the file consists of.
Definition genericcontainer.h:99
TagParser::GenericContainer::tags
const std::vector< std::unique_ptr< TagType > > & tags() const
Returns the tags of the file.
Definition genericcontainer.h:208
TagParser::Id3v1Tag
Implementation of TagParser::Tag for ID3v1 tags.
Definition id3v1tag.h:10
TagParser::Id3v2Tag
Implementation of TagParser::Tag for ID3v2 tags.
Definition id3v2tag.h:78
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
TagParser::MatroskaContainer
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
Definition matroskacontainer.h:24
TagParser::MediaFileInfo::isForcingRewrite
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
Definition mediafileinfo.h:558
TagParser::MediaFileInfo::id3v2Tags
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
Definition mediafileinfo.h:402
TagParser::MediaFileInfo::parseEverything
void parseEverything(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the container format, the tracks and the tag information of the current file.
Definition mediafileinfo.cpp:584
TagParser::MediaFileInfo::matroskaTags
const std::vector< std::unique_ptr< MatroskaTag > > & matroskaTags() const
Returns pointers to the assigned Matroska tags.
Definition mediafileinfo.cpp:1308
TagParser::MediaFileInfo::containerFormatAbbreviation
std::string_view containerFormatAbbreviation() const
Returns the abbreviation of the container format.
Definition mediafileinfo.cpp:809
TagParser::MediaFileInfo::tracks
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
Definition mediafileinfo.cpp:890
TagParser::MediaFileInfo::areChaptersSupported
bool areChaptersSupported() const
Returns an indication whether this library supports parsing the chapters of the current file.
Definition mediafileinfo.cpp:1213
TagParser::MediaFileInfo::tagsParsingStatus
ParsingStatus tagsParsingStatus() const
Returns an indication whether tag information has been parsed yet.
Definition mediafileinfo.h:325
TagParser::MediaFileInfo::saveFilePath
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
Definition mediafileinfo.h:448
TagParser::MediaFileInfo::createVorbisComment
VorbisComment * createVorbisComment()
Creates a Vorbis comment for the current file.
Definition mediafileinfo.cpp:1494
TagParser::MediaFileInfo::vorbisComment
VorbisComment * vorbisComment() const
Returns a pointer to the first assigned Vorbis comment or nullptr if none is assigned.
Definition mediafileinfo.cpp:1324
TagParser::MediaFileInfo::removeAllTags
void removeAllTags()
Removes all assigned tags from the file.
Definition mediafileinfo.cpp:1198
TagParser::MediaFileInfo::~MediaFileInfo
~MediaFileInfo() override
Destroys the MediaFileInfo.
Definition mediafileinfo.cpp:123
TagParser::MediaFileInfo::removeId3v1Tag
bool removeId3v1Tag()
Removes a possibly assigned ID3v1 tag from the current file.
Definition mediafileinfo.cpp:1044
TagParser::MediaFileInfo::hasTracksOfType
bool hasTracksOfType(TagParser::MediaType type) const
Returns an indication whether the current file has tracks of the specified type.
Definition mediafileinfo.cpp:920
TagParser::MediaFileInfo::invalidated
void invalidated() override
Reimplemented from BasicFileInfo::invalidated().
Definition mediafileinfo.cpp:1650
TagParser::MediaFileInfo::trackCount
std::size_t trackCount() const
Returns the number of tracks that could be parsed.
Definition mediafileinfo.h:346
TagParser::MediaFileInfo::parseAttachments
void parseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the attachments of the current file.
Definition mediafileinfo.cpp:554
TagParser::MediaFileInfo::parseTracks
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the current file.
Definition mediafileinfo.cpp:324
TagParser::MediaFileInfo::tags
std::vector< Tag * > tags() const
Returns all tags assigned to the current file.
Definition mediafileinfo.cpp:1578
TagParser::MediaFileInfo::removeId3v2Tag
bool removeId3v2Tag(Id3v2Tag *tag)
Removes an assigned ID3v2 tag from the current file.
Definition mediafileinfo.cpp:1092
TagParser::MediaFileInfo::areAttachmentsSupported
bool areAttachmentsSupported() const
Returns an indication whether this library supports attachment format of the current file.
Definition mediafileinfo.cpp:1230
TagParser::MediaFileInfo::parseContainerFormat
void parseContainerFormat(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the container format of the current file.
Definition mediafileinfo.cpp:140
TagParser::MediaFileInfo::maxPadding
std::size_t maxPadding() const
Returns the maximum padding to be written before the data blocks when applying changes.
Definition mediafileinfo.h:611
TagParser::MediaFileInfo::tracksParsingStatus
ParsingStatus tracksParsingStatus() const
Returns an indication whether tracks have been parsed yet.
Definition mediafileinfo.h:333
TagParser::MediaFileInfo::id3v1Tag
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
Definition mediafileinfo.h:390
TagParser::MediaFileInfo::technicalSummary
std::string technicalSummary() const
Generates a short technical summary about the file's tracks.
Definition mediafileinfo.cpp:1016
TagParser::MediaFileInfo::duration
CppUtilities::TimeSpan duration() const
Returns the overall duration of the file if known; otherwise returns a TimeSpan with zero ticks.
Definition mediafileinfo.cpp:946
TagParser::MediaFileInfo::parseChapters
void parseChapters(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the chapters of the current file.
Definition mediafileinfo.cpp:521
TagParser::MediaFileInfo::preferredPadding
std::size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
Definition mediafileinfo.h:632
TagParser::MediaFileInfo::removeVorbisComment
bool removeVorbisComment()
Removes all assigned Vorbis comment from the current file.
Definition mediafileinfo.cpp:1521
TagParser::MediaFileInfo::parseTags
void parseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tag(s) of the current file.
Definition mediafileinfo.cpp:402
TagParser::MediaFileInfo::id3v2ToId3v1
bool id3v2ToId3v1()
Converts the existing ID3v2 tags into an ID3v1 tag.
Definition mediafileinfo.cpp:1471
TagParser::MediaFileInfo::createId3v2Tag
Id3v2Tag * createId3v2Tag()
Creates an ID3v2 tag for the current file.
Definition mediafileinfo.cpp:1138
TagParser::MediaFileInfo::areTracksSupported
bool areTracksSupported() const
Returns an indication whether this library supports parsing the tracks information of the current fil...
Definition mediafileinfo.cpp:1247
TagParser::MediaFileInfo::createAppropriateTags
bool createAppropriateTags(const TagCreationSettings &settings=TagCreationSettings())
Ensures appropriate tags are created according the given settings.
Definition mediafileinfo.cpp:616
TagParser::MediaFileInfo::clearParsingResults
void clearParsingResults()
Clears all parsing results and assigned/created/changed information such as detected container format...
Definition mediafileinfo.cpp:1378
TagParser::MediaFileInfo::tags
void tags(std::vector< Tag * > &tags) const
Stores all tags assigned to the current file in the specified vector.
Definition mediafileinfo.cpp:1550
TagParser::MediaFileInfo::minPadding
std::size_t minPadding() const
Returns the minimum padding to be written before the data blocks when applying changes.
Definition mediafileinfo.h:583
TagParser::MediaFileInfo::attachmentsParsingStatus
ParsingStatus attachmentsParsingStatus() const
Returns whether the attachments have been parsed yet.
Definition mediafileinfo.h:362
TagParser::MediaFileInfo::createId3v1Tag
Id3v1Tag * createId3v1Tag()
Creates an ID3v1 tag for the current file.
Definition mediafileinfo.cpp:1071
TagParser::MediaFileInfo::hasId3v2Tag
bool hasId3v2Tag() const
Returns an indication whether an ID3v2 tag is assigned.
Definition mediafileinfo.h:378
TagParser::MediaFileInfo::isForcingFullParse
bool isForcingFullParse() const
Returns an indication whether forcing a full parse is enabled.
Definition mediafileinfo.h:540
TagParser::MediaFileInfo::effectiveSize
std::uint64_t effectiveSize() const
Returns the "effective size" of the file if know; otherwise returns 0.
Definition mediafileinfo.h:317
TagParser::MediaFileInfo::availableLanguages
std::unordered_set< std::string > availableLanguages(TagParser::MediaType type=TagParser::MediaType::Audio) const
Determines the available languages for specified media type (by default MediaType::Audio).
Definition mediafileinfo.cpp:984
TagParser::MediaFileInfo::areTagsSupported
bool areTagsSupported() const
Returns an indication whether this library supports the tag format of the current file.
Definition mediafileinfo.cpp:1268
TagParser::MediaFileInfo::removeAllId3v2Tags
bool removeAllId3v2Tags()
Removes all assigned ID3v2 tags from the current file.
Definition mediafileinfo.cpp:1114
TagParser::MediaFileInfo::container
AbstractContainer * container() const
Returns the container for the current file.
Definition mediafileinfo.h:511
TagParser::MediaFileInfo::mimeType
std::string_view mimeType() const
Returns the MIME-type of the container format.
Definition mediafileinfo.cpp:863
TagParser::MediaFileInfo::containerFormat
ContainerFormat containerFormat() const
Returns the container format of the current file.
Definition mediafileinfo.h:262
TagParser::MediaFileInfo::parsedTags
std::vector< Tag * > parsedTags() const
Returns all tags parsed from the current file.
Definition mediafileinfo.cpp:1640
TagParser::MediaFileInfo::containerParsingStatus
ParsingStatus containerParsingStatus() const
Returns an indication whether the container format has been parsed yet.
Definition mediafileinfo.h:251
TagParser::MediaFileInfo::id3v1ToId3v2
bool id3v1ToId3v2()
Converts an existing ID3v1 tag into an ID3v2 tag.
Definition mediafileinfo.cpp:1452
TagParser::MediaFileInfo::overallAverageBitrate
double overallAverageBitrate() const
Returns the overall average bitrate in kbit/s of the file if known; otherwise returns 0....
Definition mediafileinfo.cpp:965
TagParser::MediaFileInfo::chapters
std::vector< AbstractChapter * > chapters() const
Returns all chapters assigned to the current file.
Definition mediafileinfo.cpp:1336
TagParser::MediaFileInfo::applyChanges
void applyChanges(Diagnostics &diag, AbortableProgressFeedback &progress)
Applies assigned/changed tag information to the current file.
Definition mediafileinfo.cpp:743
TagParser::MediaFileInfo::containerOffset
std::uint64_t containerOffset() const
Returns the actual container start offset.
Definition mediafileinfo.h:300
TagParser::MediaFileInfo::attachments
std::vector< AbstractAttachment * > attachments() const
Returns all attachments assigned to the current file.
Definition mediafileinfo.cpp:1354
TagParser::MediaFileInfo::writePadding
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
Definition mediafileinfo.cpp:1399
TagParser::MediaFileInfo::mergeId3v2Tags
void mergeId3v2Tags()
Merges the assigned ID3v2 tags into a single ID3v2 tag.
Definition mediafileinfo.cpp:1425
TagParser::MediaFileInfo::hasId3v1Tag
bool hasId3v1Tag() const
Returns an indication whether an ID3v1 tag is assigned.
Definition mediafileinfo.h:370
TagParser::MediaFileInfo::chaptersParsingStatus
ParsingStatus chaptersParsingStatus() const
Returns whether the chapters have been parsed yet.
Definition mediafileinfo.h:354
TagParser::MediaFileInfo::removeTag
bool removeTag(Tag *tag)
Removes a possibly assigned tag from the current file.
Definition mediafileinfo.cpp:1159
TagParser::MediaFileInfo::MediaFileInfo
MediaFileInfo()
Constructs a new MediaFileInfo.
Definition mediafileinfo.cpp:107
TagParser::MediaFileInfo::hasAnyTag
bool hasAnyTag() const
Returns an indication whether a tag of any format is assigned.
Definition mediafileinfo.cpp:1591
TagParser::MediaFileInfo::backupDirectory
const std::string & backupDirectory() const
Returns the directory used to store backup files.
Definition mediafileinfo.h:421
TagParser::MediaFileInfo::mp4Tag
Mp4Tag * mp4Tag() const
Returns a pointer to the assigned MP4 tag or nullptr if none is assigned.
Definition mediafileinfo.cpp:1293
TagParser::Mp4Container
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
Definition mp4container.h:18
TagParser::Mp4Tag
Implementation of TagParser::Tag for the MP4 container.
Definition mp4tag.h:97
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
TagParser::NotImplementedException
This exception is thrown when the an operation is invoked that has not been implemented yet.
Definition exceptions.h:60
TagParser::OggContainer
Implementation of TagParser::AbstractContainer for Ogg files.
Definition oggcontainer.h:134
TagParser::OggContainer::setChecksumValidationEnabled
void setChecksumValidationEnabled(bool enabled)
Sets whether checksum validation is enabled.
Definition oggcontainer.h:186
TagParser::OggContainer::tagCount
std::size_t tagCount() const override
Returns the number of tags attached to the container.
Definition oggcontainer.cpp:135
TagParser::OggContainer::removeAllTags
void removeAllTags() override
Actually just flags all tags as removed and clears all assigned fields.
Definition oggcontainer.cpp:177
TagParser::OggContainer::createTag
OggVorbisComment * createTag(const TagTarget &target=TagTarget()) override
Creates a new tag.
Definition oggcontainer.cpp:74
TagParser::OperationAbortedException
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition exceptions.h:46
TagParser::TagTarget
The TagTarget class specifies the target of a tag.
Definition tagtarget.h:20
TagParser::Tag
The Tag class is used to store, read and write tag information.
Definition tag.h:179
TagParser::Tag::insertValues
virtual std::size_t insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Definition tag.cpp:89
TagParser::VorbisComment
Implementation of TagParser::Tag for Vorbis comments.
Definition vorbiscomment.h:27
diagnostics.h
ebmlelement.h
exceptions.h
flacmetadata.h
flacstream.h
id3v1tag.h
id3v2tag.h
ivfstream.h
matroskacontainer.h
matroskatag.h
matroskatrack.h
mediafileinfo.h
mp4atom.h
mp4container.h
mp4ids.h
mp4tag.h
mp4track.h
mpegaudioframestream.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::BackupHelper::handleFailureAfterFileModifiedCanonical
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.
Definition backuphelper.cpp:241
TagParser::BackupHelper::createBackupFileCanonical
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.
Definition backuphelper.cpp:199
TagParser::SubFormats::None
@ None
Definition mediaformat.h:112
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::ElementPosition
ElementPosition
Definition settings.h:13
TagParser::ElementPosition::BeforeData
@ BeforeData
Definition settings.h:14
TagParser::containerFormatAbbreviation
TAG_PARSER_EXPORT std::string_view containerFormatAbbreviation(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown, unsigned int version=0)
Returns the abbreviation of the container format considering the specified media type and version.
Definition signature.cpp:268
TagParser::TagCreationFlags::Id3TransferValuesOnRemoval
@ Id3TransferValuesOnRemoval
Definition settings.h:35
TagParser::TagCreationFlags::MergeMultipleSuccessiveId3v2Tags
@ MergeMultipleSuccessiveId3v2Tags
Definition settings.h:36
TagParser::TagCreationFlags::Id3InitOnCreate
@ Id3InitOnCreate
Definition settings.h:34
TagParser::TagCreationFlags::TreatUnknownFilesAsMp3Files
@ TreatUnknownFilesAsMp3Files
Definition settings.h:33
TagParser::TagCreationFlags::KeepExistingId3v2Version
@ KeepExistingId3v2Version
Definition settings.h:37
TagParser::parseSignature
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize)
Definition signature.h:83
TagParser::StereoMode::Unknown
@ Unknown
Definition abstracttrack.h:77
TagParser::containerMimeType
TAG_PARSER_EXPORT std::string_view containerMimeType(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown)
Returns the MIME-type of the container format.
Definition signature.cpp:525
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::DiagLevel::Information
@ Information
Definition diagnostics.h:19
TagParser::ParsingStatus
ParsingStatus
The ParsingStatus enum specifies whether a certain part of the file (tracks, tags,...
Definition mediafileinfo.h:40
TagParser::ParsingStatus::CriticalFailure
@ CriticalFailure
Definition mediafileinfo.h:44
TagParser::ParsingStatus::NotParsedYet
@ NotParsedYet
Definition mediafileinfo.h:41
TagParser::ParsingStatus::NotSupported
@ NotSupported
Definition mediafileinfo.h:43
TagParser::ParsingStatus::Ok
@ Ok
Definition mediafileinfo.h:42
TagParser::TagUsage::Always
@ Always
Definition settings.h:23
TagParser::TagUsage::Never
@ Never
Definition settings.h:25
TagParser::MediaType
MediaType
The MediaType enum specifies the type of media data (audio, video, text, ...).
Definition mediaformat.h:14
TagParser::MediaType::Video
@ Video
Definition mediaformat.h:17
TagParser::MediaType::Unknown
@ Unknown
Definition mediaformat.h:15
TagParser::MediaType::Audio
@ Audio
Definition mediaformat.h:16
TagParser::TagDataType::TimeSpan
@ TimeSpan
Definition tagvalue.h:124
TagParser::MediaFileStructureFlags
MediaFileStructureFlags
The MediaFileStructureFlags enum specifies flags which describing the structure of a media file.
Definition mediafileinfo.h:50
TagParser::MediaFileStructureFlags::None
@ None
Definition mediafileinfo.h:51
TagParser::MediaFileStructureFlags::ActualExistingId3v1Tag
@ ActualExistingId3v1Tag
Definition mediafileinfo.h:52
TagParser::GeneralMediaFormat::Speex
@ Speex
Definition mediaformat.h:83
TagParser::GeneralMediaFormat::Opus
@ Opus
Definition mediaformat.h:72
TagParser::ContainerFormat
ContainerFormat
Specifies the container format.
Definition signature.h:18
TagParser::ContainerFormat::Ebml
@ Ebml
Definition signature.h:26
TagParser::ContainerFormat::ApeTag
@ ApeTag
Definition signature.h:70
TagParser::ContainerFormat::Webm
@ Webm
Definition signature.h:62
TagParser::ContainerFormat::Ivf
@ Ivf
Definition signature.h:34
TagParser::ContainerFormat::Flac
@ Flac
Definition signature.h:28
TagParser::ContainerFormat::Ogg
@ Ogg
Definition signature.h:45
TagParser::ContainerFormat::Mp4
@ Mp4
Definition signature.h:43
TagParser::ContainerFormat::RiffWave
@ RiffWave
Definition signature.h:53
TagParser::ContainerFormat::Unknown
@ Unknown
Definition signature.h:19
TagParser::ContainerFormat::Matroska
@ Matroska
Definition signature.h:40
TagParser::ContainerFormat::WavPack
@ WavPack
Definition signature.h:61
TagParser::ContainerFormat::Aiff
@ Aiff
Definition signature.h:68
TagParser::ContainerFormat::MpegAudioFrames
@ MpegAudioFrames
Definition signature.h:44
TagParser::ContainerFormat::Id3v2Tag
@ Id3v2Tag
Definition signature.h:33
TagParser::ContainerFormat::Adts
@ Adts
Definition signature.h:21
TagParser::ContainerFormat::QuickTime
@ QuickTime
Definition signature.h:49
TagParser::ContainerFormat::Tar
@ Tar
Definition signature.h:55
TagParser::MediaFileHandlingFlags
MediaFileHandlingFlags
The MediaFileHandlingFlags enum specifies flags which controls the behavior of MediaFileInfo objects.
Definition mediafileinfo.h:58
TagParser::MediaFileHandlingFlags::ForceIndexPosition
@ ForceIndexPosition
Definition mediafileinfo.h:64
TagParser::MediaFileHandlingFlags::ForceTagPosition
@ ForceTagPosition
Definition mediafileinfo.h:63
TagParser::MediaFileHandlingFlags::NormalizeKnownTagFieldIds
@ NormalizeKnownTagFieldIds
Definition mediafileinfo.h:65
TagParser::MediaFileHandlingFlags::PreserveRawTimingValues
@ PreserveRawTimingValues
Definition mediafileinfo.h:66
TagParser::MediaFileHandlingFlags::ForceRewrite
@ ForceRewrite
Definition mediafileinfo.h:62
TagParser::TrackType::FlacStream
@ FlacStream
Definition abstracttrack.h:40
oggcontainer.h
progressfeedback.h
signature.h
TagParser::Locale::someAbbreviatedName
const LocaleDetail & someAbbreviatedName(LocaleFormat preferredFormat=LocaleFormat::BCP_47) const
Returns some abbreviated name, preferably of the specified preferredFormat.
Definition localehelper.cpp:85
TagParser::MediaFileInfoPrivate
The MediaFileInfoPrivate struct contains private fields of the MediaFileInfo class.
Definition mediafileinfo.cpp:64
TagParser::TagCreationSettings
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
Definition settings.h:50
TagParser::TagCreationSettings::requiredTargets
std::vector< TagTarget > requiredTargets
Specifies the required targets. If targets are not supported by the container an informal notificatio...
Definition settings.h:52
TagParser::TagCreationSettings::id3v2MajorVersion
std::uint8_t id3v2MajorVersion
Specifies the ID3v2 version to be used in case an ID3v2 tag present or will be created....
Definition settings.h:61
TagParser::TagCreationSettings::id3v2usage
TagUsage id3v2usage
Specifies the usage of ID3v2 when creating tags for MP3 files (has no effect when the file is no MP3 ...
Definition settings.h:59
TagParser::TagCreationSettings::flags
TagCreationFlags flags
Specifies options to control the tag creation. See TagSettings::Flags.
Definition settings.h:54
TagParser::TagCreationSettings::id3v1usage
TagUsage id3v1usage
Specifies the usage of ID3v1 when creating tags for MP3 files (has no effect when the file is no MP3 ...
Definition settings.h:57
tag.h
waveaudiostream.h