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;
151 std::streamoff id3v2Size = 0;
155 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
156startParsingSignature:
162 stream().seekg(m_containerOffset, ios_base::beg);
163 stream().read(buff,
sizeof(buff));
170 std::size_t bytesSkipped = 0;
171 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
173 if (bytesSkipped >= 4) {
175 m_containerOffset +=
static_cast<std::streamoff
>(bytesSkipped);
176 m_paddingSize += bytesSkipped;
179 if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
182 m_containerOffset = id3v2Size;
187 goto startParsingSignature;
191 switch ((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
194 m_actualId3v2TagOffsets.push_back(m_containerOffset);
195 if (m_actualId3v2TagOffsets.size() == 2) {
196 diag.emplace_back(
DiagLevel::Warning,
"There is more than just one ID3v2 header at the beginning of the file.", context);
200 stream().seekg(m_containerOffset + 5, ios_base::beg);
204 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
205 if ((*buff) & 0x10) {
207 m_containerOffset += 10;
209 id3v2Size = m_containerOffset;
212 goto startParsingSignature;
217 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
219 static_cast<Mp4Container *
>(m_container.get())->validateElementStructure(diag, progress, &m_paddingSize);
221 diag.emplace_back(
DiagLevel::Information,
"Validating the MP4 element structure has been aborted.", context);
229 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
240 container->validateElementStructure(diag, progress, &m_paddingSize);
241 container->validateIndex(diag, progress);
244 diag.emplace_back(
DiagLevel::Information,
"Validating the Matroska element structure has been aborted.", context);
253 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
259 if (
size() > 0x107) {
262 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
276 if (bytesSkippedBeforeContainer) {
277 diag.emplace_back(
DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer,
" bytes of junk skipped"), context);
307 static const string context(
"parsing tracks");
312 m_container->parseTracks(diag, progress);
318 switch (m_containerFormat) {
320 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
323 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
326 m_singleTrack = make_unique<IvfStream>(
stream(), m_containerOffset);
329 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
332 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
337 m_singleTrack->parseHeader(diag, progress);
340 switch (m_containerFormat) {
350 diag.emplace_back(
DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
378 static const string context(
"parsing tag");
382 m_id3v1Tag = make_unique<Id3v1Tag>();
384 stream().seekg(-128, ios_base::end);
385 m_id3v1Tag->parse(
stream(), diag);
400 for (
const auto offset : m_actualId3v2TagOffsets) {
401 auto id3v2Tag = make_unique<Id3v2Tag>();
402 stream().seekg(offset, ios_base::beg);
404 id3v2Tag->parse(
stream(),
size() -
static_cast<std::uint64_t
>(offset), diag);
405 m_paddingSize += id3v2Tag->paddingSize();
415 m_id3v2Tags.emplace_back(id3v2Tag.release());
423 m_tagsParsingStatus = m_tracksParsingStatus;
426 }
else if (m_container) {
427 m_container->parseTags(diag, progress);
442 diag.emplace_back(
DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
444 diag.emplace_back(
DiagLevel::Information,
"Parsing tags from container/streams has been aborted.", context);
467 static const string context(
"parsing chapters");
474 m_container->parseChapters(diag, progress);
478 diag.emplace_back(
DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
500 static const string context(
"parsing attachments");
507 m_container->parseAttachments(diag, progress);
511 diag.emplace_back(
DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
565 const auto flags(settings.
flags);
566 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
567 auto targetsSupported =
false;
570 if (targetsRequired) {
572 if (m_container->tagCount()) {
574 targetsSupported = m_container->tag(0)->supportsTarget();
577 auto *
const tag = m_container->createTag();
578 if (tag && (targetsSupported = tag->supportsTarget())) {
579 tag->setTarget(requiredTargets.front());
582 if (targetsSupported) {
583 for (
const auto &target : requiredTargets) {
584 m_container->createTag(target);
589 m_container->createTag();
595 switch (m_containerFormat) {
617 for (
const auto &id3v2Tag :
id3v2Tags()) {
630 id3v2Tag->insertValues(*
id3v1Tag(),
true);
685 static const string context(
"making file");
687 bool previousParsingSuccessful =
true;
693 previousParsingSuccessful =
false;
694 diag.emplace_back(
DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
701 previousParsingSuccessful =
false;
702 diag.emplace_back(
DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
704 if (!previousParsingSuccessful) {
710 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
713 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
718 m_container->makeFile(diag, progress);
727 makeMp3File(diag, progress);
752 unsigned int version = 0;
753 switch (m_containerFormat) {
760 bool onlyOpus =
true, onlySpeex =
true;
774 }
else if (onlySpeex) {
785 version = m_singleTrack->format().sub;
806 switch (m_containerFormat) {
832 vector<AbstractTrack *> res;
834 size_t containerTrackCount = 0;
839 trackCount += (containerTrackCount = m_container->trackCount());
844 res.push_back(m_singleTrack.get());
846 for (
size_t i = 0; i != containerTrackCount; ++i) {
847 res.push_back(m_container->track(i));
865 if (m_singleTrack && m_singleTrack->mediaType() == type) {
867 }
else if (m_container) {
868 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
869 if (m_container->track(i)->mediaType() == type) {
889 return m_container->duration();
890 }
else if (m_singleTrack) {
891 return m_singleTrack->duration();
911 return 0.0078125 *
static_cast<double>(
size()) /
duration.totalSeconds();
926 unordered_set<string> res;
928 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
934 res.emplace(language);
937 }
else if (m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type)) {
938 if (
const auto &language = m_singleTrack->locale().someAbbreviatedName(); !language.empty()) {
939 res.emplace(language);
959 const size_t trackCount = m_container->trackCount();
960 vector<string> parts;
963 const string description(m_container->track(i)->description());
964 if (!description.empty()) {
965 parts.emplace_back(move(description));
968 return joinStrings(parts,
" / ");
969 }
else if (m_singleTrack) {
970 return m_singleTrack->description();
1017 m_id3v1Tag = make_unique<Id3v1Tag>();
1019 return m_id3v1Tag.get();
1037 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1038 if (i->get() == tag) {
1039 m_id3v2Tags.erase(i);
1059 m_id3v2Tags.clear();
1080 if (m_id3v2Tags.empty()) {
1081 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1083 return m_id3v2Tags.front().get();
1107 return m_container->removeTag(tag);
1112 auto *
const flacStream(
static_cast<FlacStream *
>(m_singleTrack.get()));
1113 if (flacStream->vorbisComment() == tag) {
1114 return flacStream->removeVorbisComment();
1119 if (m_id3v1Tag.get() == tag) {
1123 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1124 if (i->get() == tag) {
1125 m_id3v2Tags.erase(i);
1141 m_container->removeAllTags();
1147 m_id3v2Tags.clear();
1155 if (m_container && m_container->chapterCount()) {
1158 switch (m_containerFormat) {
1172 if (m_container && m_container->attachmentCount()) {
1175 switch (m_containerFormat) {
1192 switch (m_containerFormat) {
1210 switch (m_containerFormat) {
1237 && m_container->tagCount() > 0
1254 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1278 vector<AbstractChapter *> res;
1280 const size_t count = m_container->chapterCount();
1282 for (
size_t i = 0; i != count; ++i) {
1283 res.push_back(m_container->chapter(i));
1296 vector<AbstractAttachment *> res;
1298 const size_t count = m_container->attachmentCount();
1300 for (
size_t i = 0; i != count; ++i) {
1301 res.push_back(m_container->attachment(i));
1322 m_containerOffset = 0;
1329 m_id3v2Tags.clear();
1330 m_actualId3v2TagOffsets.clear();
1332 m_container.reset();
1333 m_singleTrack.reset();
1354 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1359 auto isecond = begin + 1;
1360 if (isecond == end) {
1363 for (
auto i = isecond; i != end; ++i) {
1366 m_id3v2Tags.erase(isecond, end);
1423 switch (m_containerFormat) {
1430 if (m_singleTrack) {
1450 switch (m_containerFormat) {
1453 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1459 if (m_singleTrack) {
1480 tags.push_back(m_id3v1Tag.get());
1482 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1483 tags.push_back(tag.get());
1491 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1492 tags.push_back(m_container->tag(i));
1507 auto res = vector<Tag *>();
1536 tags.push_back(m_id3v1Tag.get());
1538 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1540 tags.push_back(tag.get());
1551 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1552 if (
auto *
const tag = m_container->tag(i); tag->size()) {
1553 tags.push_back(tag);
1569 auto res = vector<Tag *>();
1588 static const string context(
"making MP3/FLAC file");
1591 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1600 progress.
updateStep(
"Removing ID3v1 tag ...");
1605 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1606 throw std::ios_base::failure(
"Unable to truncate file to remove ID3v1 tag.");
1612 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1615 stream().seekp(-128, ios_base::end);
1617 m_id3v1Tag->make(
stream(), diag);
1618 }
catch (
const Failure &) {
1622 progress.
updateStep(
"Adding new ID3v1 tag ...");
1625 stream().seekp(0, ios_base::end);
1627 m_id3v1Tag->make(
stream(), diag);
1628 }
catch (
const Failure &) {
1640 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1643 vector<Id3v2TagMaker> makers;
1644 makers.reserve(m_id3v2Tags.size());
1645 std::uint64_t tagsSize = 0;
1646 for (
auto &tag : m_id3v2Tags) {
1648 makers.emplace_back(tag->prepareMaking(diag));
1649 tagsSize += makers.back().requiredSize();
1650 }
catch (
const Failure &) {
1655 std::uint32_t streamOffset;
1656 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1657 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1658 std::streamoff startOfLastMetaDataBlock;
1661 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1662 tagsSize +=
static_cast<std::uint64_t
>(flacMetaData.tellp());
1663 streamOffset = flacStream->streamOffset();
1666 streamOffset =
static_cast<std::uint32_t
>(m_containerOffset);
1670 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1672 if (!rewriteRequired) {
1675 padding = streamOffset - tagsSize;
1678 rewriteRequired =
true;
1681 if (makers.empty() && !flacStream) {
1687 rewriteRequired =
true;
1689 }
else if (rewriteRequired) {
1693 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1697 rewriteRequired =
true;
1699 if (rewriteRequired && flacStream && makers.empty() && padding) {
1704 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1708 string originalPath =
path(), backupPath;
1709 NativeFileStream &outputStream =
stream();
1710 NativeFileStream backupStream;
1712 if (rewriteRequired) {
1713 if (m_saveFilePath.empty()) {
1718 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
1719 }
catch (
const std::ios_base::failure &failure) {
1721 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1728 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1731 }
catch (
const std::ios_base::failure &failure) {
1732 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1742 }
catch (
const std::ios_base::failure &failure) {
1743 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1752 if (padding > numeric_limits<std::uint32_t>::max()) {
1753 padding = numeric_limits<std::uint32_t>::max();
1755 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1758 if (!makers.empty()) {
1760 progress.
updateStep(
"Writing ID3v2 tag ...");
1761 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1762 i->make(outputStream, 0, diag);
1765 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 :
static_cast<std::uint32_t
>(padding), diag);
1769 if (padding && startOfLastMetaDataBlock) {
1771 flacMetaData.seekg(startOfLastMetaDataBlock);
1772 flacMetaData.seekp(startOfLastMetaDataBlock);
1773 flacMetaData.put(
static_cast<std::uint8_t
>(flacMetaData.peek()) & (0x80u - 1));
1774 flacMetaData.seekg(0);
1778 outputStream << flacMetaData.rdbuf();
1782 flacStream->makePadding(outputStream,
static_cast<std::uint32_t
>(padding),
true, diag);
1786 if (makers.empty() && !flacStream) {
1788 for (; padding; --padding) {
1789 outputStream.put(0);
1795 std::uint64_t mediaDataSize =
size() - streamOffset;
1797 mediaDataSize -= 128;
1800 if (rewriteRequired) {
1802 switch (m_containerFormat) {
1804 progress.
updateStep(
"Writing MPEG audio frames ...");
1809 backupStream.seekg(
static_cast<streamoff
>(streamOffset));
1810 CopyHelper<0x4000> copyHelper;
1815 outputStream.seekp(
static_cast<std::streamoff
>(mediaDataSize), ios_base::cur);
1820 progress.
updateStep(
"Writing ID3v1 tag ...");
1822 m_id3v1Tag->make(
stream(), diag);
1823 }
catch (
const Failure &) {
1829 if (rewriteRequired) {
1835 m_saveFilePath.clear();
1839 outputStream.close();
1841 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1842 if (newSize <
size()) {
1845 outputStream.close();
1855 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.
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
@ PreserveRawTimingValues
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 ...