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:
170 if (
size() - m_containerOffset >= 16) {
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) {
299 static const string context(
"parsing tracks");
302 m_container->parseTracks(diag);
304 switch (m_containerFormat) {
306 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
309 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
312 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
315 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
320 m_singleTrack->parseHeader(diag);
322 switch (m_containerFormat) {
332 diag.emplace_back(
DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
359 static const string context(
"parsing tag");
362 m_id3v1Tag = make_unique<Id3v1Tag>();
364 stream().seekg(-128, ios_base::end);
365 m_id3v1Tag->parse(
stream(), diag);
366 m_actualExistingId3v1Tag =
true;
376 for (
const auto offset : m_actualId3v2TagOffsets) {
377 auto id3v2Tag = make_unique<Id3v2Tag>();
378 stream().seekg(offset, ios_base::beg);
380 id3v2Tag->parse(
stream(),
size() - offset, diag);
381 m_paddingSize += id3v2Tag->paddingSize();
388 m_id3v2Tags.emplace_back(id3v2Tag.release());
392 m_container->parseTags(diag);
398 diag.emplace_back(
DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
426 static const string context(
"parsing chapters");
429 m_container->parseChapters(diag);
436 diag.emplace_back(
DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
459 static const string context(
"parsing attachments");
462 m_container->parseAttachments(diag);
469 diag.emplace_back(
DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
511 const auto flags(settings.
flags);
512 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
513 auto targetsSupported =
false;
516 if (targetsRequired) {
518 if (m_container->tagCount()) {
520 targetsSupported = m_container->tag(0)->supportsTarget();
523 auto *
const tag = m_container->createTag();
524 if (tag && (targetsSupported = tag->supportsTarget())) {
525 tag->setTarget(requiredTargets.front());
528 if (targetsSupported) {
529 for (
const auto &target : requiredTargets) {
530 m_container->createTag(target);
535 m_container->createTag();
541 switch (m_containerFormat) {
562 for (
const auto &id3v2Tag :
id3v2Tags()) {
575 id3v2Tag->insertValues(*
id3v1Tag(),
true);
630 static const string context(
"making file");
632 bool previousParsingSuccessful =
true;
638 previousParsingSuccessful =
false;
639 diag.emplace_back(
DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
646 previousParsingSuccessful =
false;
647 diag.emplace_back(
DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
649 if (!previousParsingSuccessful) {
655 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
658 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
663 m_container->makeFile(diag, progress);
672 makeMp3File(diag, progress);
698 switch (m_containerFormat) {
705 bool onlyOpus =
true, onlySpeex =
true;
706 for (
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
719 }
else if (onlySpeex) {
730 version = m_singleTrack->format().sub;
751 switch (m_containerFormat) {
777 vector<AbstractTrack *> res;
779 size_t containerTrackCount = 0;
784 trackCount += (containerTrackCount = m_container->trackCount());
789 res.push_back(m_singleTrack.get());
791 for (
size_t i = 0; i != containerTrackCount; ++i) {
792 res.push_back(m_container->track(i));
810 if (m_singleTrack && m_singleTrack->mediaType() == type) {
812 }
else if (m_container) {
813 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
814 if (m_container->track(i)->mediaType() == type) {
834 return m_container->duration();
835 }
else if (m_singleTrack) {
836 return m_singleTrack->duration();
853 unordered_set<string> res;
855 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
861 }
else if (m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty()
862 && m_singleTrack->language() !=
"und") {
863 res.emplace(m_singleTrack->language());
882 const size_t trackCount = m_container->trackCount();
883 vector<string> parts;
886 const string description(m_container->track(i)->description());
891 return joinStrings(parts,
" / ");
892 }
else if (m_singleTrack) {
893 return m_singleTrack->description();
940 m_id3v1Tag = make_unique<Id3v1Tag>();
942 return m_id3v1Tag.get();
963 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
964 if (i->get() == tag) {
965 m_id3v2Tags.erase(i);
1006 if (m_id3v2Tags.empty()) {
1007 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1009 return m_id3v2Tags.front().get();
1030 m_container->removeTag(tag);
1032 if (m_id3v1Tag.get() == tag) {
1035 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1036 if (i->get() == tag) {
1037 m_id3v2Tags.erase(i);
1051 m_container->removeAllTags();
1057 m_id3v2Tags.clear();
1065 if (m_container && m_container->chapterCount()) {
1068 switch (m_containerFormat) {
1082 if (m_container && m_container->attachmentCount()) {
1085 switch (m_containerFormat) {
1102 switch (m_containerFormat) {
1120 switch (m_containerFormat) {
1147 && m_container->tagCount() > 0
1164 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1188 vector<AbstractChapter *> res;
1190 const size_t count = m_container->chapterCount();
1192 for (
size_t i = 0; i != count; ++i) {
1193 res.push_back(m_container->chapter(i));
1206 vector<AbstractAttachment *> res;
1208 const size_t count = m_container->attachmentCount();
1210 for (
size_t i = 0; i != count; ++i) {
1211 res.push_back(m_container->attachment(i));
1232 m_containerOffset = 0;
1239 m_id3v2Tags.clear();
1240 m_actualId3v2TagOffsets.clear();
1241 m_actualExistingId3v1Tag =
false;
1242 m_container.reset();
1243 m_singleTrack.reset();
1264 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1269 auto isecond = begin + 1;
1270 if (isecond == end) {
1273 for (
auto i = isecond; i != end; ++i) {
1276 m_id3v2Tags.erase(isecond, end - 1);
1333 switch (m_containerFormat) {
1340 if (m_singleTrack) {
1360 switch (m_containerFormat) {
1363 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1369 if (m_singleTrack) {
1389 tags.push_back(m_id3v1Tag.get());
1391 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1392 tags.push_back(tag.get());
1400 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1401 tags.push_back(m_container->tag(i));
1442 static const string context(
"making MP3/FLAC file");
1444 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1446 if (m_actualExistingId3v1Tag) {
1450 progress.
updateStep(
"Updating ID3v1 tag ...");
1453 stream().seekp(-128, ios_base::end);
1455 m_id3v1Tag->make(
stream(), diag);
1456 }
catch (
const Failure &) {
1461 progress.
updateStep(
"Removing ID3v1 tag ...");
1463 if (truncate(
path().c_str(),
size() - 128) == 0) {
1466 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1467 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1477 stream().seekp(0, ios_base::end);
1479 m_id3v1Tag->make(
stream(), diag);
1480 }
catch (
const Failure &) {
1490 progress.
updateStep(
"Updating ID3v2 tags ...");
1493 vector<Id3v2TagMaker> makers;
1494 makers.reserve(m_id3v2Tags.size());
1495 uint32 tagsSize = 0;
1496 for (
auto &tag : m_id3v2Tags) {
1498 makers.emplace_back(tag->prepareMaking(diag));
1499 tagsSize += makers.back().requiredSize();
1500 }
catch (
const Failure &) {
1506 uint32 streamOffset;
1507 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1508 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1509 uint32 startOfLastMetaDataBlock;
1513 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1514 tagsSize += flacMetaData.tellp();
1515 streamOffset = flacStream->streamOffset();
1518 streamOffset =
static_cast<uint32
>(m_containerOffset);
1522 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1524 if (!rewriteRequired) {
1527 padding = streamOffset - tagsSize;
1530 rewriteRequired =
true;
1533 if (makers.empty() && !flacStream) {
1539 rewriteRequired =
true;
1541 }
else if (rewriteRequired) {
1545 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1549 rewriteRequired =
true;
1551 if (rewriteRequired && flacStream && makers.empty() && padding) {
1556 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1561 NativeFileStream &outputStream =
stream();
1562 NativeFileStream backupStream;
1564 if (rewriteRequired) {
1565 if (m_saveFilePath.empty()) {
1570 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1572 const char *what = catchIoFailure();
1573 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
1574 throwIoFailure(what);
1580 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1581 backupStream.open(
path(), ios_base::in | ios_base::binary);
1582 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1584 const char *what = catchIoFailure();
1585 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
1586 throwIoFailure(what);
1594 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1596 const char *what = catchIoFailure();
1597 diag.emplace_back(
DiagLevel::Critical,
"Opening the file with write permissions failed.", context);
1598 throwIoFailure(what);
1604 if (!makers.empty()) {
1606 progress.
updateStep(
"Writing ID3v2 tag ...");
1607 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1608 i->make(outputStream, 0, diag);
1611 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding, diag);
1615 if (padding && startOfLastMetaDataBlock) {
1617 flacMetaData.seekg(startOfLastMetaDataBlock);
1618 flacMetaData.seekp(startOfLastMetaDataBlock);
1619 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1620 flacMetaData.seekg(0);
1624 outputStream << flacMetaData.rdbuf();
1628 flacStream->makePadding(outputStream, padding,
true, diag);
1632 if (makers.empty() && !flacStream) {
1634 for (; padding; --padding) {
1635 outputStream.put(0);
1641 uint64 mediaDataSize =
size() - streamOffset;
1642 if (m_actualExistingId3v1Tag) {
1643 mediaDataSize -= 128;
1646 if (rewriteRequired) {
1648 switch (m_containerFormat) {
1650 progress.
updateStep(
"Writing MPEG audio frames ...");
1655 backupStream.seekg(streamOffset);
1661 outputStream.seekp(mediaDataSize, ios_base::cur);
1666 progress.
updateStep(
"Writing ID3v1 tag ...");
1668 m_id3v1Tag->make(
stream(), diag);
1669 }
catch (
const Failure &) {
1675 if (rewriteRequired) {
1681 m_saveFilePath.clear();
1684 outputStream.close();
1686 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1687 if (newSize <
size()) {
1690 outputStream.close();
1692 if (truncate(
path().c_str(), newSize) == 0) {
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
void open(bool readOnly=false)
Opens a std::fstream for the current file.
TAG_PARSER_EXPORT const char * description()
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
const std::string & documentType() const
Returns a string that describes the document type if available; otherwise returns an empty string...
void updateStepPercentage(byte stepPercentage)
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.
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.
Contains utility classes helping to read and write streams.
MediaType mediaType() const
Returns the media type if known; otherwise returns MediaType::Other.
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...
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)
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.
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
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.
void parseHeader(Diagnostics &diag)
Parses the header if not parsed yet.
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.
TagUsage id3v2usage
Specifies the usage of ID3v2 when creating tags for MP3 files (has no effect when the file is no MP3 ...