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 res.push_back(m_singleTrack.get());
784 for(
size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
785 res.push_back(m_container->track(i));
802 if(m_singleTrack && m_singleTrack->mediaType() == type) {
804 }
else if(m_container) {
805 for(
size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
806 if(m_container->track(i)->mediaType() == type) {
828 res = m_container->duration();
831 if(track->duration() > res) {
832 res = track->duration();
880 m_id3v1Tag = make_unique<Id3v1Tag>();
882 return m_id3v1Tag.get();
901 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
902 if(i->get() == tag) {
903 m_id3v2Tags.erase(i);
945 if(m_id3v2Tags.empty()) {
946 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
948 return m_id3v2Tags.front().get();
967 m_container->removeTag(tag);
969 if(m_id3v1Tag.get() == tag) {
972 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
973 if(i->get() == tag) {
974 m_id3v2Tags.erase(i);
989 m_container->removeAllTags();
1003 if(m_container && m_container->chapterCount()) {
1006 switch(m_containerFormat) {
1020 if(m_container && m_container->attachmentCount()) {
1023 switch(m_containerFormat) {
1040 switch(m_containerFormat) {
1058 switch(m_containerFormat) {
1098 static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1124 vector<AbstractChapter *> res;
1126 size_t count = m_container->chapterCount();
1128 for(
size_t i = 0; i != count; ++i) {
1129 res.push_back(m_container->chapter(i));
1142 vector<AbstractAttachment *> res;
1144 size_t count = m_container->attachmentCount();
1146 for(
size_t i = 0; i < count; ++i) {
1147 res.push_back(m_container->attachment(i));
1160 if(m_container->hasNotifications()) {
1164 for(
const auto *track :
tracks()) {
1165 if(track->hasNotifications()) {
1169 for(
const auto *tag :
tags()) {
1170 if(tag->hasNotifications()) {
1174 for(
const auto *chapter :
chapters()) {
1175 if(chapter->hasNotifications()) {
1193 type |= m_container->worstNotificationType();
1198 for(
const auto *track :
tracks()) {
1199 type |= track->worstNotificationType();
1204 for(
const auto *tag :
tags()) {
1205 type |= tag->worstNotificationType();
1210 for(
const auto *chapter :
chapters()) {
1211 type |= chapter->worstNotificationType();
1228 notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
1230 for(
const auto *track :
tracks()) {
1231 notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1233 for(
const auto *tag :
tags()) {
1234 notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1236 for(
const auto *chapter :
chapters()) {
1237 notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1240 notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1268 m_containerOffset = 0;
1275 m_id3v2Tags.clear();
1276 m_actualId3v2TagOffsets.clear();
1277 m_actualExistingId3v1Tag =
false;
1278 m_container.reset();
1279 m_singleTrack.reset();
1300 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1303 auto isecond = begin + 1;
1304 if(isecond != end) {
1305 for(
auto i = isecond; i != end; ++i) {
1308 m_id3v2Tags.erase(isecond, end - 1);
1369 switch(m_containerFormat) {
1397 switch(m_containerFormat) {
1400 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1427 tags.push_back(m_id3v1Tag.get());
1429 for(
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1430 tags.push_back(tag.get());
1438 for(
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1439 tags.push_back(m_container->tag(i));
1451 || (m_container && m_container->tagCount())
1482 void MediaFileInfo::makeMp3File()
1484 static const string context(
"making MP3/FLAC file");
1487 if(m_actualExistingId3v1Tag) {
1494 stream().seekp(-128, ios_base::end);
1496 m_id3v1Tag->make(
stream());
1504 if(truncate(
path().c_str(),
size() - 128) == 0) {
1508 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1518 stream().seekp(0, ios_base::end);
1520 m_id3v1Tag->make(
stream());
1534 vector<Id3v2TagMaker> makers;
1535 makers.reserve(m_id3v2Tags.size());
1536 uint32 tagsSize = 0;
1537 for(
auto &tag : m_id3v2Tags) {
1539 makers.emplace_back(tag->prepareMaking());
1540 tagsSize += makers.back().requiredSize();
1549 uint32 streamOffset;
1550 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1551 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1552 uint32 startOfLastMetaDataBlock;
1556 startOfLastMetaDataBlock = flacStream->
makeHeader(flacMetaData);
1557 tagsSize += flacMetaData.tellp();
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 updateStatus(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 *what = catchIoFailure();
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 *what = catchIoFailure();
1629 throwIoFailure(what);
1637 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1639 const char *what = catchIoFailure();
1641 throwIoFailure(what);
1647 if(!makers.empty()) {
1650 for(
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1651 i->make(outputStream, 0);
1654 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1658 if(padding && startOfLastMetaDataBlock) {
1660 flacMetaData.seekg(startOfLastMetaDataBlock);
1661 flacMetaData.seekp(startOfLastMetaDataBlock);
1662 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1663 flacMetaData.seekg(0);
1667 outputStream << flacMetaData.rdbuf();
1671 flacStream->
makePadding(outputStream, padding,
true);
1675 if(makers.empty() && !flacStream){
1677 for(; padding; --padding) {
1678 outputStream.put(0);
1684 uint64 mediaDataSize =
size() - streamOffset;
1685 if(m_actualExistingId3v1Tag) {
1686 mediaDataSize -= 128;
1689 if(rewriteRequired) {
1691 switch(m_containerFormat) {
1698 backupStream.seekg(streamOffset);
1704 outputStream.seekp(mediaDataSize, ios_base::cur);
1711 m_id3v1Tag->make(
stream());
1718 if(rewriteRequired) {
1724 m_saveFilePath.clear();
1727 outputStream.close();
1729 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1730 if(newSize <
size()) {
1733 outputStream.close();
1735 if(truncate(
path().c_str(), newSize) == 0) {
Contains utility classes helping to read and write streams.