37 #include <c++utilities/chrono/timespan.h> 38 #include <c++utilities/conversion/stringconversion.h> 39 #include <c++utilities/io/catchiofailure.h> 49 #include <system_error> 52 using namespace std::placeholders;
54 using namespace ConversionUtilities;
55 using namespace ChronoUtilities;
64 #ifdef FORCE_FULL_PARSE_DEFAULT 65 #define MEDIAINFO_CPP_FORCE_FULL_PARSE true 67 #define MEDIAINFO_CPP_FORCE_FULL_PARSE false 83 MediaFileInfo::MediaFileInfo()
86 , m_containerOffset(0)
87 , m_actualExistingId3v1Tag(false)
94 , m_preferredPadding(0)
98 , m_forceRewrite(true)
99 , m_forceTagPosition(true)
100 , m_forceIndexPosition(true)
113 , m_containerOffset(0)
114 , m_actualExistingId3v1Tag(false)
121 , m_preferredPadding(0)
125 , m_forceRewrite(true)
126 , m_forceTagPosition(true)
127 , m_forceIndexPosition(true)
160 static const string context(
"parsing file header");
166 m_containerOffset = 0;
170 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
171 startParsingSignature:
173 stream().seekg(m_containerOffset, ios_base::beg);
174 stream().read(buff,
sizeof(buff));
177 size_t bytesSkipped = 0;
178 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
180 if (bytesSkipped >= 4) {
181 m_containerOffset += bytesSkipped;
184 if ((m_paddingSize += bytesSkipped) >= 0x100u) {
191 goto startParsingSignature;
194 diag.emplace_back(
DiagLevel::Warning, argsToString(m_paddingSize,
" zero-bytes skipped at the beginning of the file."), context);
198 switch ((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
201 m_actualId3v2TagOffsets.push_back(m_containerOffset);
202 if (m_actualId3v2TagOffsets.size() == 2) {
203 diag.emplace_back(
DiagLevel::Warning,
"There is more than just one ID3v2 header at the beginning of the file.", context);
207 stream().seekg(m_containerOffset + 5, ios_base::beg);
211 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
212 if ((*buff) & 0x10) {
214 m_containerOffset += 10;
218 goto startParsingSignature;
223 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
225 static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
233 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
241 if (m_forceFullParse) {
244 container->validateElementStructure(diag, &m_paddingSize);
255 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
256 static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
261 if (
size() > 0x107) {
264 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
303 static const string context(
"parsing tracks");
308 m_container->parseTracks(diag);
314 switch (m_containerFormat) {
316 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
319 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
322 m_singleTrack = make_unique<IvfStream>(
stream(), m_containerOffset);
325 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
328 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
333 m_singleTrack->parseHeader(diag);
336 switch (m_containerFormat) {
338 m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->
paddingSize();
346 diag.emplace_back(
DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
374 static const string context(
"parsing tag");
378 m_id3v1Tag = make_unique<Id3v1Tag>();
380 stream().seekg(-128, ios_base::end);
381 m_id3v1Tag->parse(
stream(), diag);
382 m_actualExistingId3v1Tag =
true;
393 for (
const auto offset : m_actualId3v2TagOffsets) {
394 auto id3v2Tag = make_unique<Id3v2Tag>();
395 stream().seekg(offset, ios_base::beg);
397 id3v2Tag->parse(
stream(),
size() - static_cast<uint64>(offset), diag);
398 m_paddingSize += id3v2Tag->paddingSize();
405 m_id3v2Tags.emplace_back(id3v2Tag.release());
413 m_tagsParsingStatus = m_tracksParsingStatus;
416 }
else if (m_container) {
417 m_container->parseTags(diag);
432 diag.emplace_back(
DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
456 static const string context(
"parsing chapters");
463 m_container->parseChapters(diag);
467 diag.emplace_back(
DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
491 static const string context(
"parsing attachments");
498 m_container->parseAttachments(diag);
502 diag.emplace_back(
DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
544 const auto flags(settings.
flags);
545 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
546 auto targetsSupported =
false;
549 if (targetsRequired) {
551 if (m_container->tagCount()) {
553 targetsSupported = m_container->tag(0)->supportsTarget();
556 auto *
const tag = m_container->createTag();
557 if (tag && (targetsSupported = tag->supportsTarget())) {
558 tag->setTarget(requiredTargets.front());
561 if (targetsSupported) {
562 for (
const auto &target : requiredTargets) {
563 m_container->createTag(target);
568 m_container->createTag();
574 switch (m_containerFormat) {
595 for (
const auto &id3v2Tag :
id3v2Tags()) {
608 id3v2Tag->insertValues(*
id3v1Tag(),
true);
663 static const string context(
"making file");
665 bool previousParsingSuccessful =
true;
671 previousParsingSuccessful =
false;
672 diag.emplace_back(
DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
679 previousParsingSuccessful =
false;
680 diag.emplace_back(
DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
682 if (!previousParsingSuccessful) {
688 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
691 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
696 m_container->makeFile(diag, progress);
705 makeMp3File(diag, progress);
731 switch (m_containerFormat) {
734 const auto &
tracks = static_cast<OggContainer *>(m_container.get())->
tracks();
738 bool onlyOpus =
true, onlySpeex =
true;
739 for (
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
752 }
else if (onlySpeex) {
763 version = m_singleTrack->format().sub;
784 switch (m_containerFormat) {
810 vector<AbstractTrack *> res;
812 size_t containerTrackCount = 0;
817 trackCount += (containerTrackCount = m_container->trackCount());
822 res.push_back(m_singleTrack.get());
824 for (
size_t i = 0; i != containerTrackCount; ++i) {
825 res.push_back(m_container->track(i));
843 if (m_singleTrack && m_singleTrack->mediaType() == type) {
845 }
else if (m_container) {
846 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
847 if (m_container->track(i)->mediaType() == type) {
867 return m_container->duration();
868 }
else if (m_singleTrack) {
869 return m_singleTrack->duration();
904 unordered_set<string> res;
906 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
912 }
else if (m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
913 && m_singleTrack->language() !=
"und") {
914 res.emplace(m_singleTrack->language());
933 const size_t trackCount = m_container->trackCount();
934 vector<string> parts;
937 const string description(m_container->track(i)->description());
942 return joinStrings(parts,
" / ");
943 }
else if (m_singleTrack) {
944 return m_singleTrack->description();
991 m_id3v1Tag = make_unique<Id3v1Tag>();
993 return m_id3v1Tag.get();
1011 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1012 if (i->get() == tag) {
1013 m_id3v2Tags.erase(i);
1033 m_id3v2Tags.clear();
1054 if (m_id3v2Tags.empty()) {
1055 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1057 return m_id3v2Tags.front().get();
1081 return m_container->removeTag(tag);
1086 auto *
const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1087 if (flacStream->vorbisComment() == tag) {
1088 return flacStream->removeVorbisComment();
1093 if (m_id3v1Tag.get() == tag) {
1097 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1098 if (i->get() == tag) {
1099 m_id3v2Tags.erase(i);
1115 m_container->removeAllTags();
1121 m_id3v2Tags.clear();
1129 if (m_container && m_container->chapterCount()) {
1132 switch (m_containerFormat) {
1146 if (m_container && m_container->attachmentCount()) {
1149 switch (m_containerFormat) {
1166 switch (m_containerFormat) {
1184 switch (m_containerFormat) {
1211 && m_container->tagCount() > 0
1212 ? static_cast<Mp4Container *>(m_container.get())->
tags().front().get()
1226 return static_cast<MatroskaContainer *>(m_container.get())->
tags();
1228 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1241 ? static_cast<OggContainer *>(m_container.get())->
tags().front().get()
1252 vector<AbstractChapter *> res;
1254 const size_t count = m_container->chapterCount();
1256 for (
size_t i = 0; i != count; ++i) {
1257 res.push_back(m_container->chapter(i));
1270 vector<AbstractAttachment *> res;
1272 const size_t count = m_container->attachmentCount();
1274 for (
size_t i = 0; i != count; ++i) {
1275 res.push_back(m_container->attachment(i));
1296 m_containerOffset = 0;
1303 m_id3v2Tags.clear();
1304 m_actualId3v2TagOffsets.clear();
1305 m_actualExistingId3v1Tag =
false;
1306 m_container.reset();
1307 m_singleTrack.reset();
1328 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1333 auto isecond = begin + 1;
1334 if (isecond == end) {
1337 for (
auto i = isecond; i != end; ++i) {
1340 m_id3v2Tags.erase(isecond, end - 1);
1397 switch (m_containerFormat) {
1400 return static_cast<OggContainer *>(m_container.get())->createTag(
TagTarget());
1404 if (m_singleTrack) {
1424 switch (m_containerFormat) {
1427 bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1428 static_cast<OggContainer *>(m_container.get())->
removeAllTags();
1433 if (m_singleTrack) {
1453 tags.push_back(m_id3v1Tag.get());
1455 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1456 tags.push_back(tag.get());
1464 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1465 tags.push_back(m_container->tag(i));
1506 static const string context(
"making MP3/FLAC file");
1509 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1514 if (!m_actualExistingId3v1Tag) {
1518 progress.
updateStep(
"Removing ID3v1 tag ...");
1520 if (truncate(
path().data(), static_cast<std::streamoff>(
size() - 128)) == 0) {
1523 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1524 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1529 if (m_actualExistingId3v1Tag) {
1530 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1533 stream().seekp(-128, ios_base::end);
1535 m_id3v1Tag->make(
stream(), diag);
1536 }
catch (
const Failure &) {
1540 progress.
updateStep(
"Adding new ID3v1 tag ...");
1543 stream().seekp(0, ios_base::end);
1545 m_id3v1Tag->make(
stream(), diag);
1546 }
catch (
const Failure &) {
1556 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1559 vector<Id3v2TagMaker> makers;
1560 makers.reserve(m_id3v2Tags.size());
1561 uint32 tagsSize = 0;
1562 for (
auto &tag : m_id3v2Tags) {
1564 makers.emplace_back(tag->prepareMaking(diag));
1565 tagsSize += makers.back().requiredSize();
1566 }
catch (
const Failure &) {
1571 uint32 streamOffset;
1572 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1573 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1574 std::streamoff startOfLastMetaDataBlock;
1577 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1578 tagsSize += flacMetaData.tellp();
1579 streamOffset = flacStream->streamOffset();
1582 streamOffset = static_cast<uint32>(m_containerOffset);
1586 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1588 if (!rewriteRequired) {
1591 padding = streamOffset - tagsSize;
1594 rewriteRequired =
true;
1597 if (makers.empty() && !flacStream) {
1603 rewriteRequired =
true;
1605 }
else if (rewriteRequired) {
1609 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1613 rewriteRequired =
true;
1615 if (rewriteRequired && flacStream && makers.empty() && padding) {
1620 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1625 NativeFileStream &outputStream =
stream();
1626 NativeFileStream backupStream;
1628 if (rewriteRequired) {
1629 if (m_saveFilePath.empty()) {
1634 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1636 const char *
const what = catchIoFailure();
1637 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
1638 throwIoFailure(what);
1644 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1645 backupStream.open(
path(), ios_base::in | ios_base::binary);
1646 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1648 const char *
const what = catchIoFailure();
1649 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
1650 throwIoFailure(what);
1658 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1660 const char *
const what = catchIoFailure();
1661 diag.emplace_back(
DiagLevel::Critical,
"Opening the file with write permissions failed.", context);
1662 throwIoFailure(what);
1669 if (padding > numeric_limits<uint32>::max()) {
1670 padding = numeric_limits<uint32>::max();
1672 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1675 if (!makers.empty()) {
1677 progress.
updateStep(
"Writing ID3v2 tag ...");
1678 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1679 i->make(outputStream, 0, diag);
1682 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<uint32>(padding), diag);
1686 if (padding && startOfLastMetaDataBlock) {
1688 flacMetaData.seekg(startOfLastMetaDataBlock);
1689 flacMetaData.seekp(startOfLastMetaDataBlock);
1690 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1691 flacMetaData.seekg(0);
1695 outputStream << flacMetaData.rdbuf();
1699 flacStream->makePadding(outputStream, static_cast<uint32>(padding),
true, diag);
1703 if (makers.empty() && !flacStream) {
1705 for (; padding; --padding) {
1706 outputStream.put(0);
1712 uint64 mediaDataSize =
size() - streamOffset;
1713 if (m_actualExistingId3v1Tag) {
1714 mediaDataSize -= 128;
1717 if (rewriteRequired) {
1719 switch (m_containerFormat) {
1721 progress.
updateStep(
"Writing MPEG audio frames ...");
1726 backupStream.seekg(static_cast<streamoff>(streamOffset));
1732 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1737 progress.
updateStep(
"Writing ID3v1 tag ...");
1739 m_id3v1Tag->make(
stream(), diag);
1740 }
catch (
const Failure &) {
1746 if (rewriteRequired) {
1752 m_saveFilePath.clear();
1755 outputStream.close();
1757 const auto newSize = static_cast<uint64>(outputStream.tellp());
1758 if (newSize <
size()) {
1761 outputStream.close();
1763 if (truncate(
path().c_str(), static_cast<streamoff>(newSize)) == 0) {
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
This exception is thrown when the an operation is invoked that has not been implemented yet.
void open(bool readOnly=false)
Opens a std::fstream for the current file.
Implementation of TagParser::Tag for the MP4 container.
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
The Tag class is used to store, read and write tag information.
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string.
void updateStepPercentage(byte stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
const std::string & path() const
Returns the path of the current file.
MediaType
The MediaType enum specifies the type of media data (audio, video, text, ...).
std::vector< TagTarget > requiredTargets
Specifies the required targets. If targets are not supported by the container an informal notificatio...
TagCreationFlags flags
Specifies options to control the tag creation. See TagSettings::Flags.
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
TagUsage id3v1usage
Specifies the usage of ID3v1 when creating tags for MP3 files (has no effect when the file is no MP3 ...
TAG_PARSER_EXPORT const char * containerMimeType(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown)
Returns the MIME-type of the container format as C-style string.
virtual void invalidated()
This function is called when the BasicFileInfo gets invalidated.
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
byte id3v2MajorVersion
Specifies the ID3v2 version to be used in case an ID3v2 tag present or will be created....
ContainerFormat
Specifies the container format.
Contains utility classes helping to read and write streams.
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
The exception that is thrown when the data to be parsed holds no parsable information (e....
The TagTarget class specifies the target of a tag.
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
TAG_PARSER_EXPORT const char * 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...
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
void close()
A possibly opened std::fstream will be closed.
ParsingStatus
The ParsingStatus enum specifies whether a certain part of the file (tracks, tags,...
void updateStep(const std::string &step, byte stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
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...
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
Implementation of TagParser::Tag for ID3v1 tags.
int insertFields(const FieldMapBasedTag< ImplementationType > &from, bool overwrite)
Inserts all fields from another tag of the same field type and compare function.
uint64 size() const
Returns size of the current file in bytes.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
void parseHeader(Diagnostics &diag)
Parses the header if not parsed yet.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
constexpr TAG_PARSER_EXPORT const char * description()
virtual unsigned int insertValues(const Tag &from, bool overwrite)
Inserts all compatible values from another Tag.
Contains all classes and functions of the TagInfo library.
TagUsage id3v2usage
Specifies the usage of ID3v2 when creating tags for MP3 files (has no effect when the file is no MP3 ...
The Diagnostics class is a container for DiagMessage.
The BasicFileInfo class provides basic file information such as file name, extension,...