35 #include <c++utilities/chrono/timespan.h> 36 #include <c++utilities/conversion/stringconversion.h> 37 #include <c++utilities/io/catchiofailure.h> 47 #include <system_error> 62 #ifdef FORCE_FULL_PARSE_DEFAULT 63 #define MEDIAINFO_CPP_FORCE_FULL_PARSE true 65 #define MEDIAINFO_CPP_FORCE_FULL_PARSE false 81 MediaFileInfo::MediaFileInfo()
84 , m_containerOffset(0)
85 , m_actualExistingId3v1Tag(false)
92 , m_preferredPadding(0)
96 , m_forceRewrite(true)
97 , m_forceTagPosition(true)
98 , m_forceIndexPosition(true)
111 , m_containerOffset(0)
112 , m_actualExistingId3v1Tag(false)
119 , m_preferredPadding(0)
123 , m_forceRewrite(true)
124 , m_forceTagPosition(true)
125 , m_forceIndexPosition(true)
158 static const string context(
"parsing file header");
164 m_containerOffset = 0;
168 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
169 startParsingSignature:
171 stream().seekg(m_containerOffset, ios_base::beg);
172 stream().read(buff,
sizeof(buff));
175 size_t bytesSkipped = 0;
176 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
178 if (bytesSkipped >= 4) {
179 m_containerOffset += bytesSkipped;
182 if ((m_paddingSize += bytesSkipped) >= 0x100u) {
189 goto startParsingSignature;
192 diag.emplace_back(
DiagLevel::Warning, argsToString(m_paddingSize,
" zero-bytes skipped at the beginning of the file."), context);
196 switch ((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
199 m_actualId3v2TagOffsets.push_back(m_containerOffset);
200 if (m_actualId3v2TagOffsets.size() == 2) {
201 diag.emplace_back(
DiagLevel::Warning,
"There is more than just one ID3v2 header at the beginning of the file.", context);
205 stream().seekg(m_containerOffset + 5, ios_base::beg);
209 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
210 if ((*buff) & 0x10) {
212 m_containerOffset += 10;
216 goto startParsingSignature;
221 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
223 static_cast<Mp4Container *
>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
231 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
239 if (m_forceFullParse) {
242 container->validateElementStructure(diag, &m_paddingSize);
253 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
254 static_cast<OggContainer *
>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
259 if (
size() > 0x107) {
262 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
301 static const string context(
"parsing tracks");
306 m_container->parseTracks(diag);
312 switch (m_containerFormat) {
314 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
317 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
320 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
323 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
328 m_singleTrack->parseHeader(diag);
331 switch (m_containerFormat) {
341 diag.emplace_back(
DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
369 static const string context(
"parsing tag");
373 m_id3v1Tag = make_unique<Id3v1Tag>();
375 stream().seekg(-128, ios_base::end);
376 m_id3v1Tag->parse(
stream(), diag);
377 m_actualExistingId3v1Tag =
true;
388 for (
const auto offset : m_actualId3v2TagOffsets) {
389 auto id3v2Tag = make_unique<Id3v2Tag>();
390 stream().seekg(offset, ios_base::beg);
392 id3v2Tag->parse(
stream(),
size() - static_cast<uint64>(offset), diag);
393 m_paddingSize += id3v2Tag->paddingSize();
400 m_id3v2Tags.emplace_back(id3v2Tag.release());
408 m_tagsParsingStatus = m_tracksParsingStatus;
411 }
else if (m_container) {
412 m_container->parseTags(diag);
427 diag.emplace_back(
DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
451 static const string context(
"parsing chapters");
458 m_container->parseChapters(diag);
462 diag.emplace_back(
DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
486 static const string context(
"parsing attachments");
493 m_container->parseAttachments(diag);
497 diag.emplace_back(
DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
539 const auto flags(settings.
flags);
540 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
541 auto targetsSupported =
false;
544 if (targetsRequired) {
546 if (m_container->tagCount()) {
548 targetsSupported = m_container->tag(0)->supportsTarget();
551 auto *
const tag = m_container->createTag();
552 if (tag && (targetsSupported = tag->supportsTarget())) {
553 tag->setTarget(requiredTargets.front());
556 if (targetsSupported) {
557 for (
const auto &target : requiredTargets) {
558 m_container->createTag(target);
563 m_container->createTag();
569 switch (m_containerFormat) {
590 for (
const auto &id3v2Tag :
id3v2Tags()) {
603 id3v2Tag->insertValues(*
id3v1Tag(),
true);
658 static const string context(
"making file");
660 bool previousParsingSuccessful =
true;
666 previousParsingSuccessful =
false;
667 diag.emplace_back(
DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
674 previousParsingSuccessful =
false;
675 diag.emplace_back(
DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
677 if (!previousParsingSuccessful) {
683 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
686 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
691 m_container->makeFile(diag, progress);
700 makeMp3File(diag, progress);
726 switch (m_containerFormat) {
733 bool onlyOpus =
true, onlySpeex =
true;
734 for (
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
747 }
else if (onlySpeex) {
758 version = m_singleTrack->format().sub;
779 switch (m_containerFormat) {
805 vector<AbstractTrack *> res;
807 size_t containerTrackCount = 0;
812 trackCount += (containerTrackCount = m_container->trackCount());
817 res.push_back(m_singleTrack.get());
819 for (
size_t i = 0; i != containerTrackCount; ++i) {
820 res.push_back(m_container->track(i));
838 if (m_singleTrack && m_singleTrack->mediaType() == type) {
840 }
else if (m_container) {
841 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
842 if (m_container->track(i)->mediaType() == type) {
862 return m_container->duration();
863 }
else if (m_singleTrack) {
864 return m_singleTrack->duration();
881 unordered_set<string> res;
883 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
889 }
else if (m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
890 && m_singleTrack->language() !=
"und") {
891 res.emplace(m_singleTrack->language());
910 const size_t trackCount = m_container->trackCount();
911 vector<string> parts;
914 const string description(m_container->track(i)->description());
919 return joinStrings(parts,
" / ");
920 }
else if (m_singleTrack) {
921 return m_singleTrack->description();
968 m_id3v1Tag = make_unique<Id3v1Tag>();
970 return m_id3v1Tag.get();
988 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
989 if (i->get() == tag) {
990 m_id3v2Tags.erase(i);
1010 m_id3v2Tags.clear();
1031 if (m_id3v2Tags.empty()) {
1032 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1034 return m_id3v2Tags.front().get();
1059 m_container->removeTag(tag);
1065 auto *
const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1066 if (flacStream->vorbisComment() == tag) {
1067 flacStream->removeVorbisComment();
1073 if (m_id3v1Tag.get() == tag) {
1077 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1078 if (i->get() == tag) {
1079 m_id3v2Tags.erase(i);
1094 m_container->removeAllTags();
1100 m_id3v2Tags.clear();
1108 if (m_container && m_container->chapterCount()) {
1111 switch (m_containerFormat) {
1125 if (m_container && m_container->attachmentCount()) {
1128 switch (m_containerFormat) {
1145 switch (m_containerFormat) {
1163 switch (m_containerFormat) {
1190 && m_container->tagCount() > 0
1207 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1231 vector<AbstractChapter *> res;
1233 const size_t count = m_container->chapterCount();
1235 for (
size_t i = 0; i != count; ++i) {
1236 res.push_back(m_container->chapter(i));
1249 vector<AbstractAttachment *> res;
1251 const size_t count = m_container->attachmentCount();
1253 for (
size_t i = 0; i != count; ++i) {
1254 res.push_back(m_container->attachment(i));
1275 m_containerOffset = 0;
1282 m_id3v2Tags.clear();
1283 m_actualId3v2TagOffsets.clear();
1284 m_actualExistingId3v1Tag =
false;
1285 m_container.reset();
1286 m_singleTrack.reset();
1307 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1312 auto isecond = begin + 1;
1313 if (isecond == end) {
1316 for (
auto i = isecond; i != end; ++i) {
1319 m_id3v2Tags.erase(isecond, end - 1);
1376 switch (m_containerFormat) {
1383 if (m_singleTrack) {
1403 switch (m_containerFormat) {
1406 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1412 if (m_singleTrack) {
1432 tags.push_back(m_id3v1Tag.get());
1434 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1435 tags.push_back(tag.get());
1443 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1444 tags.push_back(m_container->tag(i));
1485 static const string context(
"making MP3/FLAC file");
1488 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1493 if (!m_actualExistingId3v1Tag) {
1497 progress.
updateStep(
"Removing ID3v1 tag ...");
1499 if (truncate(
path().data(), static_cast<std::streamoff>(
size() - 128)) == 0) {
1502 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1503 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1508 if (m_actualExistingId3v1Tag) {
1509 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1512 stream().seekp(-128, ios_base::end);
1514 m_id3v1Tag->make(
stream(), diag);
1515 }
catch (
const Failure &) {
1519 progress.
updateStep(
"Adding new ID3v1 tag ...");
1522 stream().seekp(0, ios_base::end);
1524 m_id3v1Tag->make(
stream(), diag);
1525 }
catch (
const Failure &) {
1535 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1538 vector<Id3v2TagMaker> makers;
1539 makers.reserve(m_id3v2Tags.size());
1540 uint32 tagsSize = 0;
1541 for (
auto &tag : m_id3v2Tags) {
1543 makers.emplace_back(tag->prepareMaking(diag));
1544 tagsSize += makers.back().requiredSize();
1545 }
catch (
const Failure &) {
1550 uint32 streamOffset;
1551 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1552 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1553 uint32 startOfLastMetaDataBlock;
1556 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1557 tagsSize +=
static_cast<uint32
>(flacMetaData.tellp());
1558 streamOffset = flacStream->streamOffset();
1561 streamOffset =
static_cast<uint32
>(m_containerOffset);
1565 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1567 if (!rewriteRequired) {
1570 padding = streamOffset - tagsSize;
1573 rewriteRequired =
true;
1576 if (makers.empty() && !flacStream) {
1582 rewriteRequired =
true;
1584 }
else if (rewriteRequired) {
1588 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1592 rewriteRequired =
true;
1594 if (rewriteRequired && flacStream && makers.empty() && padding) {
1599 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1604 NativeFileStream &outputStream =
stream();
1605 NativeFileStream backupStream;
1607 if (rewriteRequired) {
1608 if (m_saveFilePath.empty()) {
1613 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1615 const char *
const what = catchIoFailure();
1616 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
1617 throwIoFailure(what);
1623 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1624 backupStream.open(
path(), ios_base::in | ios_base::binary);
1625 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1627 const char *
const what = catchIoFailure();
1628 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
1629 throwIoFailure(what);
1637 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1639 const char *
const what = catchIoFailure();
1640 diag.emplace_back(
DiagLevel::Critical,
"Opening the file with write permissions failed.", context);
1641 throwIoFailure(what);
1648 if (padding > numeric_limits<uint32>::max()) {
1649 padding = numeric_limits<uint32>::max();
1651 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1654 if (!makers.empty()) {
1656 progress.
updateStep(
"Writing ID3v2 tag ...");
1657 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1658 i->make(outputStream, 0, diag);
1661 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<uint32>(padding), diag);
1665 if (padding && startOfLastMetaDataBlock) {
1667 flacMetaData.seekg(static_cast<streamoff>(startOfLastMetaDataBlock));
1668 flacMetaData.seekp(static_cast<streamoff>(startOfLastMetaDataBlock));
1669 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1670 flacMetaData.seekg(0);
1674 outputStream << flacMetaData.rdbuf();
1678 flacStream->makePadding(outputStream, static_cast<uint32>(padding),
true, diag);
1682 if (makers.empty() && !flacStream) {
1684 for (; padding; --padding) {
1685 outputStream.put(0);
1691 uint64 mediaDataSize =
size() - streamOffset;
1692 if (m_actualExistingId3v1Tag) {
1693 mediaDataSize -= 128;
1696 if (rewriteRequired) {
1698 switch (m_containerFormat) {
1700 progress.
updateStep(
"Writing MPEG audio frames ...");
1705 backupStream.seekg(static_cast<streamoff>(streamOffset));
1711 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1716 progress.
updateStep(
"Writing ID3v1 tag ...");
1718 m_id3v1Tag->make(
stream(), diag);
1719 }
catch (
const Failure &) {
1725 if (rewriteRequired) {
1731 m_saveFilePath.clear();
1734 outputStream.close();
1736 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1737 if (newSize <
size()) {
1740 outputStream.close();
1742 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.
TAG_PARSER_EXPORT const char * description()
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.
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...
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
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.