38#include <c++utilities/chrono/timespan.h>
39#include <c++utilities/conversion/stringconversion.h>
49#include <system_error>
52using namespace std::placeholders;
81 , m_containerOffset(0)
90 , m_preferredPadding(0)
136 CPP_UTILITIES_UNUSED(progress)
143 static const string context(
"parsing file header");
149 m_containerOffset = 0;
150 std::size_t bytesSkippedBeforeContainer = 0;
154 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
155startParsingSignature:
161 stream().seekg(m_containerOffset, ios_base::beg);
162 stream().read(buff,
sizeof(buff));
169 std::size_t bytesSkipped = 0;
170 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
172 if (bytesSkipped >= 4) {
174 m_containerOffset +=
static_cast<std::streamoff
>(bytesSkipped);
175 m_paddingSize += bytesSkipped;
178 if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
185 goto startParsingSignature;
189 switch ((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
192 m_actualId3v2TagOffsets.push_back(m_containerOffset);
193 if (m_actualId3v2TagOffsets.size() == 2) {
194 diag.emplace_back(
DiagLevel::Warning,
"There is more than just one ID3v2 header at the beginning of the file.", context);
198 stream().seekg(m_containerOffset + 5, ios_base::beg);
202 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
203 if ((*buff) & 0x10) {
205 m_containerOffset += 10;
209 goto startParsingSignature;
214 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
216 static_cast<Mp4Container *
>(m_container.get())->validateElementStructure(diag, progress, &m_paddingSize);
218 diag.emplace_back(
DiagLevel::Information,
"Validating the MP4 element structure has been aborted.", context);
226 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
237 container->validateElementStructure(diag, progress, &m_paddingSize);
238 container->validateIndex(diag, progress);
241 diag.emplace_back(
DiagLevel::Information,
"Validating the Matroska element structure has been aborted.", context);
250 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
256 if (
size() > 0x107) {
259 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
273 if (bytesSkippedBeforeContainer) {
274 diag.emplace_back(
DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer,
" bytes of junk skipped"), context);
304 static const string context(
"parsing tracks");
309 m_container->parseTracks(diag, progress);
315 switch (m_containerFormat) {
317 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
320 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
323 m_singleTrack = make_unique<IvfStream>(
stream(), m_containerOffset);
326 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
329 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
334 m_singleTrack->parseHeader(diag, progress);
337 switch (m_containerFormat) {
347 diag.emplace_back(
DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
375 static const string context(
"parsing tag");
379 m_id3v1Tag = make_unique<Id3v1Tag>();
381 stream().seekg(-128, ios_base::end);
382 m_id3v1Tag->parse(
stream(), diag);
397 for (
const auto offset : m_actualId3v2TagOffsets) {
398 auto id3v2Tag = make_unique<Id3v2Tag>();
399 stream().seekg(offset, ios_base::beg);
401 id3v2Tag->parse(
stream(),
size() -
static_cast<std::uint64_t
>(offset), diag);
402 m_paddingSize += id3v2Tag->paddingSize();
412 m_id3v2Tags.emplace_back(id3v2Tag.release());
420 m_tagsParsingStatus = m_tracksParsingStatus;
423 }
else if (m_container) {
424 m_container->parseTags(diag, progress);
439 diag.emplace_back(
DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
441 diag.emplace_back(
DiagLevel::Information,
"Parsing tags from container/streams has been aborted.", context);
464 static const string context(
"parsing chapters");
471 m_container->parseChapters(diag, progress);
475 diag.emplace_back(
DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
497 static const string context(
"parsing attachments");
504 m_container->parseAttachments(diag, progress);
508 diag.emplace_back(
DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
562 const auto flags(settings.
flags);
563 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
564 auto targetsSupported =
false;
567 if (targetsRequired) {
569 if (m_container->tagCount()) {
571 targetsSupported = m_container->tag(0)->supportsTarget();
574 auto *
const tag = m_container->createTag();
575 if (tag && (targetsSupported = tag->supportsTarget())) {
576 tag->setTarget(requiredTargets.front());
579 if (targetsSupported) {
580 for (
const auto &target : requiredTargets) {
581 m_container->createTag(target);
586 m_container->createTag();
592 switch (m_containerFormat) {
613 for (
const auto &id3v2Tag :
id3v2Tags()) {
626 id3v2Tag->insertValues(*
id3v1Tag(),
true);
681 static const string context(
"making file");
683 bool previousParsingSuccessful =
true;
689 previousParsingSuccessful =
false;
690 diag.emplace_back(
DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
697 previousParsingSuccessful =
false;
698 diag.emplace_back(
DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
700 if (!previousParsingSuccessful) {
706 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
709 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
714 m_container->makeFile(diag, progress);
723 makeMp3File(diag, progress);
749 switch (m_containerFormat) {
756 bool onlyOpus =
true, onlySpeex =
true;
770 }
else if (onlySpeex) {
781 version = m_singleTrack->format().sub;
802 switch (m_containerFormat) {
828 vector<AbstractTrack *> res;
830 size_t containerTrackCount = 0;
835 trackCount += (containerTrackCount = m_container->trackCount());
840 res.push_back(m_singleTrack.get());
842 for (
size_t i = 0; i != containerTrackCount; ++i) {
843 res.push_back(m_container->track(i));
861 if (m_singleTrack && m_singleTrack->mediaType() == type) {
863 }
else if (m_container) {
864 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
865 if (m_container->track(i)->mediaType() == type) {
885 return m_container->duration();
886 }
else if (m_singleTrack) {
887 return m_singleTrack->duration();
907 return 0.0078125 *
static_cast<double>(
size()) /
duration.totalSeconds();
922 unordered_set<string> res;
924 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
933 }
else if (m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type)) {
934 if (
const auto &
language = m_singleTrack->locale().someAbbreviatedName(); !
language.empty()) {
955 const size_t trackCount = m_container->trackCount();
956 vector<string> parts;
959 const string description(m_container->track(i)->description());
964 return joinStrings(parts,
" / ");
965 }
else if (m_singleTrack) {
966 return m_singleTrack->description();
1013 m_id3v1Tag = make_unique<Id3v1Tag>();
1015 return m_id3v1Tag.get();
1033 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1034 if (i->get() == tag) {
1035 m_id3v2Tags.erase(i);
1055 m_id3v2Tags.clear();
1076 if (m_id3v2Tags.empty()) {
1077 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1079 return m_id3v2Tags.front().get();
1103 return m_container->removeTag(tag);
1108 auto *
const flacStream(
static_cast<FlacStream *
>(m_singleTrack.get()));
1109 if (flacStream->vorbisComment() == tag) {
1110 return flacStream->removeVorbisComment();
1115 if (m_id3v1Tag.get() == tag) {
1119 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1120 if (i->get() == tag) {
1121 m_id3v2Tags.erase(i);
1137 m_container->removeAllTags();
1143 m_id3v2Tags.clear();
1151 if (m_container && m_container->chapterCount()) {
1154 switch (m_containerFormat) {
1168 if (m_container && m_container->attachmentCount()) {
1171 switch (m_containerFormat) {
1188 switch (m_containerFormat) {
1206 switch (m_containerFormat) {
1233 && m_container->tagCount() > 0
1250 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1274 vector<AbstractChapter *> res;
1276 const size_t count = m_container->chapterCount();
1278 for (
size_t i = 0; i != count; ++i) {
1279 res.push_back(m_container->chapter(i));
1292 vector<AbstractAttachment *> res;
1294 const size_t count = m_container->attachmentCount();
1296 for (
size_t i = 0; i != count; ++i) {
1297 res.push_back(m_container->attachment(i));
1318 m_containerOffset = 0;
1325 m_id3v2Tags.clear();
1326 m_actualId3v2TagOffsets.clear();
1328 m_container.reset();
1329 m_singleTrack.reset();
1350 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1355 auto isecond = begin + 1;
1356 if (isecond == end) {
1359 for (
auto i = isecond; i != end; ++i) {
1362 m_id3v2Tags.erase(isecond, end - 1);
1419 switch (m_containerFormat) {
1426 if (m_singleTrack) {
1446 switch (m_containerFormat) {
1449 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1455 if (m_singleTrack) {
1476 tags.push_back(m_id3v1Tag.get());
1478 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1479 tags.push_back(tag.get());
1487 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1488 tags.push_back(m_container->tag(i));
1503 auto res = vector<Tag *>();
1532 tags.push_back(m_id3v1Tag.get());
1534 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1536 tags.push_back(tag.get());
1547 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1548 if (
auto *
const tag = m_container->tag(i); tag->size()) {
1549 tags.push_back(tag);
1565 auto res = vector<Tag *>();
1584 static const string context(
"making MP3/FLAC file");
1587 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1596 progress.
updateStep(
"Removing ID3v1 tag ...");
1601 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1602 throw std::ios_base::failure(
"Unable to truncate file to remove ID3v1 tag.");
1608 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1611 stream().seekp(-128, ios_base::end);
1613 m_id3v1Tag->make(
stream(), diag);
1614 }
catch (
const Failure &) {
1618 progress.
updateStep(
"Adding new ID3v1 tag ...");
1621 stream().seekp(0, ios_base::end);
1623 m_id3v1Tag->make(
stream(), diag);
1624 }
catch (
const Failure &) {
1636 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1639 vector<Id3v2TagMaker> makers;
1640 makers.reserve(m_id3v2Tags.size());
1641 std::uint64_t tagsSize = 0;
1642 for (
auto &tag : m_id3v2Tags) {
1644 makers.emplace_back(tag->prepareMaking(diag));
1645 tagsSize += makers.back().requiredSize();
1646 }
catch (
const Failure &) {
1651 std::uint32_t streamOffset;
1652 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1653 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1654 std::streamoff startOfLastMetaDataBlock;
1657 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1658 tagsSize +=
static_cast<std::uint64_t
>(flacMetaData.tellp());
1659 streamOffset = flacStream->streamOffset();
1662 streamOffset =
static_cast<std::uint32_t
>(m_containerOffset);
1666 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1668 if (!rewriteRequired) {
1671 padding = streamOffset - tagsSize;
1674 rewriteRequired =
true;
1677 if (makers.empty() && !flacStream) {
1683 rewriteRequired =
true;
1685 }
else if (rewriteRequired) {
1689 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1693 rewriteRequired =
true;
1695 if (rewriteRequired && flacStream && makers.empty() && padding) {
1700 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1704 string originalPath =
path(), backupPath;
1705 NativeFileStream &outputStream =
stream();
1706 NativeFileStream backupStream;
1708 if (rewriteRequired) {
1709 if (m_saveFilePath.empty()) {
1714 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1715 }
catch (
const std::ios_base::failure &failure) {
1717 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1724 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1727 }
catch (
const std::ios_base::failure &failure) {
1728 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1738 }
catch (
const std::ios_base::failure &failure) {
1739 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1748 if (padding > numeric_limits<std::uint32_t>::max()) {
1749 padding = numeric_limits<std::uint32_t>::max();
1751 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1754 if (!makers.empty()) {
1756 progress.
updateStep(
"Writing ID3v2 tag ...");
1757 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1758 i->make(outputStream, 0, diag);
1761 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 :
static_cast<std::uint32_t
>(padding), diag);
1765 if (padding && startOfLastMetaDataBlock) {
1767 flacMetaData.seekg(startOfLastMetaDataBlock);
1768 flacMetaData.seekp(startOfLastMetaDataBlock);
1769 flacMetaData.put(
static_cast<std::uint8_t
>(flacMetaData.peek()) & (0x80u - 1));
1770 flacMetaData.seekg(0);
1774 outputStream << flacMetaData.rdbuf();
1778 flacStream->makePadding(outputStream,
static_cast<std::uint32_t
>(padding),
true, diag);
1782 if (makers.empty() && !flacStream) {
1784 for (; padding; --padding) {
1785 outputStream.put(0);
1791 std::uint64_t mediaDataSize =
size() - streamOffset;
1793 mediaDataSize -= 128;
1796 if (rewriteRequired) {
1798 switch (m_containerFormat) {
1800 progress.
updateStep(
"Writing MPEG audio frames ...");
1805 backupStream.seekg(
static_cast<streamoff
>(streamOffset));
1811 outputStream.seekp(
static_cast<std::streamoff
>(mediaDataSize), ios_base::cur);
1816 progress.
updateStep(
"Writing ID3v1 tag ...");
1818 m_id3v1Tag->make(
stream(), diag);
1819 }
catch (
const Failure &) {
1825 if (rewriteRequired) {
1831 m_saveFilePath.clear();
1835 outputStream.close();
1837 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1838 if (newSize <
size()) {
1841 outputStream.close();
1851 outputStream.flush();
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...
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.
VorbisComment * vorbisComment() const
Returns the Vorbis comment if one is present in the stream.
const std::vector< std::unique_ptr< TrackType > > & tracks() const
Returns the tracks of the file.
Implementation of TagParser::Tag for ID3v1 tags.
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Implementation of TagParser::Tag for ID3v2 tags.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
Implementation of TagParser::Tag for the MP4 container.
The exception that is thrown when the data to be parsed holds no parsable information (e....
This exception is thrown when the an operation is invoked that has not been implemented yet.
Implementation of TagParser::AbstractContainer for OGG files.
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
The TagTarget class specifies the target of a tag.
The Tag class is used to store, read and write tag information.
std::uint64_t size() const
Returns the size the tag within the file it is parsed from in bytes.
virtual std::size_t insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
constexpr TAG_PARSER_EXPORT std::string_view description()
constexpr TAG_PARSER_EXPORT std::string_view language()
Contains all classes and functions of the TagInfo library.
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...
@ Id3TransferValuesOnRemoval
@ MergeMultipleSuccessiveId3v2Tags
@ TreatUnknownFilesAsMp3Files
@ KeepExistingId3v2Version
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize)
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, ...).
MediaFileStructureFlags
The MediaFileStructureFlags enum specifies flags which describing the structure of a media file.
ContainerFormat
Specifies the container format.
MediaFileHandlingFlags
The MediaFileHandlingFlags enum specifies flags which controls the behavior of MediaFileInfo objects.
@ NormalizeKnownTagFieldIds
const LocaleDetail & someAbbreviatedName(LocaleFormat preferredFormat=LocaleFormat::BCP_47) const
Returns some abbreviated name, preferably of the specified preferredFormat.
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
std::vector< TagTarget > requiredTargets
Specifies the required targets. If targets are not supported by the container an informal notificatio...
std::uint8_t id3v2MajorVersion
Specifies the ID3v2 version to be used in case an ID3v2 tag present or will be created....
TagUsage id3v2usage
Specifies the usage of ID3v2 when creating tags for MP3 files (has no effect when the file is no MP3 ...
TagCreationFlags flags
Specifies options to control the tag creation. See TagSettings::Flags.
TagUsage id3v1usage
Specifies the usage of ID3v1 when creating tags for MP3 files (has no effect when the file is no MP3 ...