38 #include <c++utilities/chrono/timespan.h>
39 #include <c++utilities/conversion/stringconversion.h>
49 #include <system_error>
52 using namespace std::placeholders;
77 MediaFileInfo::MediaFileInfo(std::string &&path)
81 , m_containerOffset(0)
90 , m_preferredPadding(0)
93 , m_fileHandlingFlags(
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;
155 startParsingSignature:
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) {
1475 tags.push_back(m_id3v1Tag.get());
1477 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1478 tags.push_back(tag.get());
1486 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1487 tags.push_back(m_container->tag(i));
1528 static const string context(
"making MP3/FLAC file");
1531 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1540 progress.
updateStep(
"Removing ID3v1 tag ...");
1545 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1546 throw std::ios_base::failure(
"Unable to truncate file to remove ID3v1 tag.");
1552 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1555 stream().seekp(-128, ios_base::end);
1557 m_id3v1Tag->make(
stream(), diag);
1558 }
catch (
const Failure &) {
1562 progress.
updateStep(
"Adding new ID3v1 tag ...");
1565 stream().seekp(0, ios_base::end);
1567 m_id3v1Tag->make(
stream(), diag);
1568 }
catch (
const Failure &) {
1580 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1583 vector<Id3v2TagMaker> makers;
1584 makers.reserve(m_id3v2Tags.size());
1585 std::uint64_t tagsSize = 0;
1586 for (
auto &tag : m_id3v2Tags) {
1588 makers.emplace_back(tag->prepareMaking(diag));
1589 tagsSize += makers.back().requiredSize();
1590 }
catch (
const Failure &) {
1595 std::uint32_t streamOffset;
1596 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1597 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1598 std::streamoff startOfLastMetaDataBlock;
1601 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1602 tagsSize +=
static_cast<std::uint64_t
>(flacMetaData.tellp());
1603 streamOffset = flacStream->streamOffset();
1606 streamOffset =
static_cast<std::uint32_t
>(m_containerOffset);
1610 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1612 if (!rewriteRequired) {
1615 padding = streamOffset - tagsSize;
1618 rewriteRequired =
true;
1621 if (makers.empty() && !flacStream) {
1627 rewriteRequired =
true;
1629 }
else if (rewriteRequired) {
1633 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1637 rewriteRequired =
true;
1639 if (rewriteRequired && flacStream && makers.empty() && padding) {
1644 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1649 NativeFileStream &outputStream =
stream();
1650 NativeFileStream backupStream;
1652 if (rewriteRequired) {
1653 if (m_saveFilePath.empty()) {
1659 }
catch (
const std::ios_base::failure &failure) {
1661 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1668 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1671 }
catch (
const std::ios_base::failure &failure) {
1672 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1682 }
catch (
const std::ios_base::failure &failure) {
1683 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1692 if (padding > numeric_limits<std::uint32_t>::max()) {
1693 padding = numeric_limits<std::uint32_t>::max();
1695 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1698 if (!makers.empty()) {
1700 progress.
updateStep(
"Writing ID3v2 tag ...");
1701 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1702 i->make(outputStream, 0, diag);
1705 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 :
static_cast<std::uint32_t
>(padding), diag);
1709 if (padding && startOfLastMetaDataBlock) {
1711 flacMetaData.seekg(startOfLastMetaDataBlock);
1712 flacMetaData.seekp(startOfLastMetaDataBlock);
1713 flacMetaData.put(
static_cast<std::uint8_t
>(flacMetaData.peek()) & (0x80u - 1));
1714 flacMetaData.seekg(0);
1718 outputStream << flacMetaData.rdbuf();
1722 flacStream->makePadding(outputStream,
static_cast<std::uint32_t
>(padding),
true, diag);
1726 if (makers.empty() && !flacStream) {
1728 for (; padding; --padding) {
1729 outputStream.put(0);
1735 std::uint64_t mediaDataSize =
size() - streamOffset;
1737 mediaDataSize -= 128;
1740 if (rewriteRequired) {
1742 switch (m_containerFormat) {
1744 progress.
updateStep(
"Writing MPEG audio frames ...");
1749 backupStream.seekg(
static_cast<streamoff
>(streamOffset));
1755 outputStream.seekp(
static_cast<std::streamoff
>(mediaDataSize), ios_base::cur);
1760 progress.
updateStep(
"Writing ID3v1 tag ...");
1762 m_id3v1Tag->make(
stream(), diag);
1763 }
catch (
const Failure &) {
1769 if (rewriteRequired) {
1775 m_saveFilePath.clear();
1779 outputStream.close();
1781 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1782 if (newSize <
size()) {
1785 outputStream.close();
1795 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.
virtual std::size_t insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
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.
const LocaleDetail & someAbbreviatedName(LocaleFormat preferredFormat=LocaleFormat::BCP_47) const
Returns some abbreviated name, preferrably 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 ...