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> 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) {
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) {
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();
886 unordered_set<string> res;
888 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
894 }
else if (m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
895 && m_singleTrack->language() !=
"und") {
896 res.emplace(m_singleTrack->language());
915 const size_t trackCount = m_container->trackCount();
916 vector<string> parts;
919 const string description(m_container->track(i)->description());
924 return joinStrings(parts,
" / ");
925 }
else if (m_singleTrack) {
926 return m_singleTrack->description();
973 m_id3v1Tag = make_unique<Id3v1Tag>();
975 return m_id3v1Tag.get();
993 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
994 if (i->get() == tag) {
995 m_id3v2Tags.erase(i);
1015 m_id3v2Tags.clear();
1036 if (m_id3v2Tags.empty()) {
1037 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1039 return m_id3v2Tags.front().get();
1063 return m_container->removeTag(tag);
1068 auto *
const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1069 if (flacStream->vorbisComment() == tag) {
1070 return flacStream->removeVorbisComment();
1075 if (m_id3v1Tag.get() == tag) {
1079 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1080 if (i->get() == tag) {
1081 m_id3v2Tags.erase(i);
1097 m_container->removeAllTags();
1103 m_id3v2Tags.clear();
1111 if (m_container && m_container->chapterCount()) {
1114 switch (m_containerFormat) {
1128 if (m_container && m_container->attachmentCount()) {
1131 switch (m_containerFormat) {
1148 switch (m_containerFormat) {
1166 switch (m_containerFormat) {
1193 && m_container->tagCount() > 0
1210 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1234 vector<AbstractChapter *> res;
1236 const size_t count = m_container->chapterCount();
1238 for (
size_t i = 0; i != count; ++i) {
1239 res.push_back(m_container->chapter(i));
1252 vector<AbstractAttachment *> res;
1254 const size_t count = m_container->attachmentCount();
1256 for (
size_t i = 0; i != count; ++i) {
1257 res.push_back(m_container->attachment(i));
1278 m_containerOffset = 0;
1285 m_id3v2Tags.clear();
1286 m_actualId3v2TagOffsets.clear();
1287 m_actualExistingId3v1Tag =
false;
1288 m_container.reset();
1289 m_singleTrack.reset();
1310 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1315 auto isecond = begin + 1;
1316 if (isecond == end) {
1319 for (
auto i = isecond; i != end; ++i) {
1322 m_id3v2Tags.erase(isecond, end - 1);
1379 switch (m_containerFormat) {
1386 if (m_singleTrack) {
1406 switch (m_containerFormat) {
1409 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1415 if (m_singleTrack) {
1435 tags.push_back(m_id3v1Tag.get());
1437 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1438 tags.push_back(tag.get());
1446 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1447 tags.push_back(m_container->tag(i));
1488 static const string context(
"making MP3/FLAC file");
1491 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1496 if (!m_actualExistingId3v1Tag) {
1500 progress.
updateStep(
"Removing ID3v1 tag ...");
1502 if (truncate(
path().data(), static_cast<std::streamoff>(
size() - 128)) == 0) {
1505 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1506 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1511 if (m_actualExistingId3v1Tag) {
1512 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1515 stream().seekp(-128, ios_base::end);
1517 m_id3v1Tag->make(
stream(), diag);
1518 }
catch (
const Failure &) {
1522 progress.
updateStep(
"Adding new ID3v1 tag ...");
1525 stream().seekp(0, ios_base::end);
1527 m_id3v1Tag->make(
stream(), diag);
1528 }
catch (
const Failure &) {
1538 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1541 vector<Id3v2TagMaker> makers;
1542 makers.reserve(m_id3v2Tags.size());
1543 uint32 tagsSize = 0;
1544 for (
auto &tag : m_id3v2Tags) {
1546 makers.emplace_back(tag->prepareMaking(diag));
1547 tagsSize += makers.back().requiredSize();
1548 }
catch (
const Failure &) {
1553 uint32 streamOffset;
1554 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1555 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1556 std::streamoff startOfLastMetaDataBlock;
1559 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1560 tagsSize += flacMetaData.tellp();
1561 streamOffset = flacStream->streamOffset();
1564 streamOffset =
static_cast<uint32
>(m_containerOffset);
1568 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1570 if (!rewriteRequired) {
1573 padding = streamOffset - tagsSize;
1576 rewriteRequired =
true;
1579 if (makers.empty() && !flacStream) {
1585 rewriteRequired =
true;
1587 }
else if (rewriteRequired) {
1591 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1595 rewriteRequired =
true;
1597 if (rewriteRequired && flacStream && makers.empty() && padding) {
1602 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1607 NativeFileStream &outputStream =
stream();
1608 NativeFileStream backupStream;
1610 if (rewriteRequired) {
1611 if (m_saveFilePath.empty()) {
1616 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1618 const char *
const what = catchIoFailure();
1619 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
1620 throwIoFailure(what);
1626 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1627 backupStream.open(
path(), ios_base::in | ios_base::binary);
1628 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1630 const char *
const what = catchIoFailure();
1631 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
1632 throwIoFailure(what);
1640 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1642 const char *
const what = catchIoFailure();
1643 diag.emplace_back(
DiagLevel::Critical,
"Opening the file with write permissions failed.", context);
1644 throwIoFailure(what);
1651 if (padding > numeric_limits<uint32>::max()) {
1652 padding = numeric_limits<uint32>::max();
1654 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1657 if (!makers.empty()) {
1659 progress.
updateStep(
"Writing ID3v2 tag ...");
1660 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1661 i->make(outputStream, 0, diag);
1664 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<uint32>(padding), diag);
1668 if (padding && startOfLastMetaDataBlock) {
1670 flacMetaData.seekg(startOfLastMetaDataBlock);
1671 flacMetaData.seekp(startOfLastMetaDataBlock);
1672 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1673 flacMetaData.seekg(0);
1677 outputStream << flacMetaData.rdbuf();
1681 flacStream->makePadding(outputStream, static_cast<uint32>(padding),
true, diag);
1685 if (makers.empty() && !flacStream) {
1687 for (; padding; --padding) {
1688 outputStream.put(0);
1694 uint64 mediaDataSize =
size() - streamOffset;
1695 if (m_actualExistingId3v1Tag) {
1696 mediaDataSize -= 128;
1699 if (rewriteRequired) {
1701 switch (m_containerFormat) {
1703 progress.
updateStep(
"Writing MPEG audio frames ...");
1708 backupStream.seekg(static_cast<streamoff>(streamOffset));
1714 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1719 progress.
updateStep(
"Writing ID3v1 tag ...");
1721 m_id3v1Tag->make(
stream(), diag);
1722 }
catch (
const Failure &) {
1728 if (rewriteRequired) {
1734 m_saveFilePath.clear();
1737 outputStream.close();
1739 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1740 if (newSize <
size()) {
1743 outputStream.close();
1745 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.
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
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.
Implementation of TagParser::AbstractTrack for raw FLAC streams.
The TagSettings struct contains settings which can be passed to MediaFileInfo::createAppropriateTags(...
Implementation of GenericContainer<MediaFileInfo, Mp4Tag, Mp4Track, Mp4Atom>.
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. Valid values are 2, 3 and 4.
ContainerFormat
Specifies the container format.
Implementation of TagParser::AbstractContainer for OGG files.
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.
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, ...) has been parsed yet and if what the parsing result is.
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, directory and size for a specified file.