33 #include <c++utilities/conversion/stringconversion.h> 34 #include <c++utilities/io/catchiofailure.h> 35 #include <c++utilities/chrono/timespan.h> 43 #include <system_error> 60 #ifdef FORCE_FULL_PARSE_DEFAULT 61 # define MEDIAINFO_CPP_FORCE_FULL_PARSE true 63 # define MEDIAINFO_CPP_FORCE_FULL_PARSE false 79 MediaFileInfo::MediaFileInfo() :
83 m_actualExistingId3v1Tag(false),
92 m_preferredPadding(0),
94 m_forceTagPosition(true),
96 m_forceIndexPosition(true)
108 m_containerOffset(0),
109 m_actualExistingId3v1Tag(false),
115 m_forceRewrite(true),
118 m_preferredPadding(0),
120 m_forceTagPosition(true),
122 m_forceIndexPosition(true)
154 static const string context(
"parsing file header");
160 m_containerOffset = 0;
164 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
165 startParsingSignature:
166 if(
size() - m_containerOffset >= 16) {
167 stream().seekg(m_containerOffset, ios_base::beg);
168 stream().read(buff,
sizeof(buff));
171 size_t bytesSkipped = 0;
172 for(buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped);
173 if(bytesSkipped >= 4) {
174 m_containerOffset += bytesSkipped;
177 if((m_paddingSize += bytesSkipped) >= 0x100u) {
184 goto startParsingSignature;
191 switch((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
194 m_actualId3v2TagOffsets.push_back(m_containerOffset);
195 if(m_actualId3v2TagOffsets.size() == 2) {
200 stream().seekg(m_containerOffset + 5, ios_base::beg);
204 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
207 m_containerOffset += 10;
211 goto startParsingSignature;
216 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
219 static_cast<Mp4Container *
>(m_container.get())->validateElementStructure(notifications, &m_paddingSize);
228 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
237 if(m_forceFullParse) {
240 container->validateElementStructure(notifications, &m_paddingSize);
251 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
252 static_cast<OggContainer *
>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
260 if(buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
298 static const string context(
"parsing tracks");
301 m_container->parseTracks();
303 switch(m_containerFormat) {
305 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
308 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
311 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
314 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
319 m_singleTrack->parseHeader();
321 switch(m_containerFormat) {
359 static const string context(
"parsing tag");
362 m_id3v1Tag = make_unique<Id3v1Tag>();
364 m_id3v1Tag->parse(
stream(),
true);
365 m_actualExistingId3v1Tag =
true;
375 for(
const auto offset : m_actualId3v2TagOffsets) {
376 auto id3v2Tag = make_unique<Id3v2Tag>();
377 stream().seekg(offset, ios_base::beg);
380 m_paddingSize += id3v2Tag->paddingSize();
387 m_id3v2Tags.emplace_back(id3v2Tag.release());
391 m_container->parseTags();
425 static const string context(
"parsing chapters");
428 m_container->parseChapters();
458 static const string context(
"parsing attachments");
461 m_container->parseAttachments();
511 bool MediaFileInfo::createAppropriateTags(
bool treatUnknownFilesAsMp3Files,
TagUsage id3v1usage,
TagUsage id3v2usage,
bool id3InitOnCreate,
bool id3TransferValuesOnRemoval,
bool mergeMultipleSuccessiveId3v2Tags,
bool keepExistingId3v2version, byte id3v2MajorVersion,
const std::vector<TagTarget> &requiredTargets)
518 bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
519 bool targetsSupported =
false;
522 if(targetsRequired) {
524 if(m_container->tagCount()) {
526 targetsSupported = m_container->tag(0)->supportsTarget();
529 auto *tag = m_container->createTag();
531 if((targetsSupported = tag->supportsTarget())) {
532 tag->setTarget(requiredTargets.front());
536 if(targetsSupported) {
537 for(
const auto &target : requiredTargets) {
538 m_container->createTag(target);
543 m_container->createTag();
552 if(!
hasAnyTag() && !treatUnknownFilesAsMp3Files) {
566 if(id3InitOnCreate) {
567 for(
const auto &id3v2Tag :
id3v2Tags()) {
588 if(mergeMultipleSuccessiveId3v2Tags) {
609 }
else if(!keepExistingId3v2version) {
612 tag->setVersion(id3v2MajorVersion, 0);
616 if(targetsRequired && !targetsSupported) {
646 const string context(
"making file");
648 bool previousParsingSuccessful =
true;
654 previousParsingSuccessful =
false;
662 previousParsingSuccessful =
false;
665 if(!previousParsingSuccessful) {
676 m_container->forwardStatusUpdateCalls(
this);
680 m_container->makeFile();
715 switch(m_containerFormat) {
719 for(
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
731 version = m_singleTrack->format().sub;
753 switch(m_containerFormat) {
779 vector<AbstractTrack *> res;
781 size_t containerTrackCount = 0;
786 trackCount += (containerTrackCount = m_container->trackCount());
788 res.reserve(trackCount);
791 res.push_back(m_singleTrack.get());
793 for(
size_t i = 0; i != containerTrackCount; ++i) {
794 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) {
836 res = m_container->duration();
839 if(track->duration() > res) {
840 res = track->duration();
888 m_id3v1Tag = make_unique<Id3v1Tag>();
890 return m_id3v1Tag.get();
909 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
910 if(i->get() == tag) {
911 m_id3v2Tags.erase(i);
953 if(m_id3v2Tags.empty()) {
954 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
956 return m_id3v2Tags.front().get();
975 m_container->removeTag(tag);
977 if(m_id3v1Tag.get() == tag) {
980 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
981 if(i->get() == tag) {
982 m_id3v2Tags.erase(i);
997 m_container->removeAllTags();
1003 m_id3v2Tags.clear();
1011 if(m_container && m_container->chapterCount()) {
1014 switch(m_containerFormat) {
1028 if(m_container && m_container->attachmentCount()) {
1031 switch(m_containerFormat) {
1048 switch(m_containerFormat) {
1066 switch(m_containerFormat) {
1106 static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1132 vector<AbstractChapter *> res;
1134 size_t count = m_container->chapterCount();
1136 for(
size_t i = 0; i != count; ++i) {
1137 res.push_back(m_container->chapter(i));
1150 vector<AbstractAttachment *> res;
1152 size_t count = m_container->attachmentCount();
1154 for(
size_t i = 0; i < count; ++i) {
1155 res.push_back(m_container->attachment(i));
1168 if(m_container->hasNotifications()) {
1172 for(
const auto *track :
tracks()) {
1173 if(track->hasNotifications()) {
1177 for(
const auto *tag :
tags()) {
1178 if(tag->hasNotifications()) {
1182 for(
const auto *chapter :
chapters()) {
1183 if(chapter->hasNotifications()) {
1201 type |= m_container->worstNotificationType();
1206 for(
const auto *track :
tracks()) {
1207 type |= track->worstNotificationType();
1212 for(
const auto *tag :
tags()) {
1213 type |= tag->worstNotificationType();
1218 for(
const auto *chapter :
chapters()) {
1219 type |= chapter->worstNotificationType();
1236 notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
1238 for(
const auto *track :
tracks()) {
1239 notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1241 for(
const auto *tag :
tags()) {
1242 notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1244 for(
const auto *chapter :
chapters()) {
1245 notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1248 notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1276 m_containerOffset = 0;
1283 m_id3v2Tags.clear();
1284 m_actualId3v2TagOffsets.clear();
1285 m_actualExistingId3v1Tag =
false;
1286 m_container.reset();
1287 m_singleTrack.reset();
1308 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1311 auto isecond = begin + 1;
1312 if(isecond != end) {
1313 for(
auto i = isecond; i != end; ++i) {
1316 m_id3v2Tags.erase(isecond, end - 1);
1377 switch(m_containerFormat) {
1405 switch(m_containerFormat) {
1408 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
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));
1459 || (m_container && m_container->tagCount())
1490 void MediaFileInfo::makeMp3File()
1492 static const string context(
"making MP3/FLAC file");
1495 if(m_actualExistingId3v1Tag) {
1502 stream().seekp(-128, ios_base::end);
1504 m_id3v1Tag->make(
stream());
1512 if(truncate(
path().c_str(),
size() - 128) == 0) {
1516 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1526 stream().seekp(0, ios_base::end);
1528 m_id3v1Tag->make(
stream());
1542 vector<Id3v2TagMaker> makers;
1543 makers.reserve(m_id3v2Tags.size());
1544 uint32 tagsSize = 0;
1545 for(
auto &tag : m_id3v2Tags) {
1547 makers.emplace_back(tag->prepareMaking());
1548 tagsSize += makers.back().requiredSize();
1557 uint32 streamOffset;
1558 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1559 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1560 uint32 startOfLastMetaDataBlock;
1564 startOfLastMetaDataBlock = flacStream->
makeHeader(flacMetaData);
1565 tagsSize += flacMetaData.tellp();
1569 streamOffset =
static_cast<uint32
>(m_containerOffset);
1573 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1575 if(!rewriteRequired) {
1578 padding = streamOffset - tagsSize;
1581 rewriteRequired =
true;
1584 if(makers.empty() && !flacStream) {
1590 rewriteRequired =
true;
1592 }
else if(rewriteRequired) {
1596 }
else if(makers.empty() && flacStream && padding && padding < 4) {
1600 rewriteRequired =
true;
1602 if(rewriteRequired && flacStream && makers.empty() && padding) {
1607 updateStatus(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1612 NativeFileStream &outputStream =
stream();
1613 NativeFileStream backupStream;
1615 if(rewriteRequired) {
1616 if(m_saveFilePath.empty()) {
1621 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1623 const char *what = catchIoFailure();
1625 throwIoFailure(what);
1631 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1632 backupStream.open(
path(), ios_base::in | ios_base::binary);
1633 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1635 const char *what = catchIoFailure();
1637 throwIoFailure(what);
1645 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1647 const char *what = catchIoFailure();
1649 throwIoFailure(what);
1655 if(!makers.empty()) {
1658 for(
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1659 i->make(outputStream, 0);
1662 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1666 if(padding && startOfLastMetaDataBlock) {
1668 flacMetaData.seekg(startOfLastMetaDataBlock);
1669 flacMetaData.seekp(startOfLastMetaDataBlock);
1670 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1671 flacMetaData.seekg(0);
1675 outputStream << flacMetaData.rdbuf();
1679 flacStream->
makePadding(outputStream, padding,
true);
1683 if(makers.empty() && !flacStream){
1685 for(; padding; --padding) {
1686 outputStream.put(0);
1692 uint64 mediaDataSize =
size() - streamOffset;
1693 if(m_actualExistingId3v1Tag) {
1694 mediaDataSize -= 128;
1697 if(rewriteRequired) {
1699 switch(m_containerFormat) {
1706 backupStream.seekg(streamOffset);
1712 outputStream.seekp(mediaDataSize, ios_base::cur);
1719 m_id3v1Tag->make(
stream());
1726 if(rewriteRequired) {
1732 m_saveFilePath.clear();
1735 outputStream.close();
1737 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1738 if(newSize <
size()) {
1741 outputStream.close();
1743 if(truncate(
path().c_str(), newSize) == 0) {
Contains utility classes helping to read and write streams.