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 static 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) {
835 return m_container->duration();
836 }
else if(m_singleTrack) {
837 return m_singleTrack->duration();
854 unordered_set<string> res;
856 for(
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
862 }
else if(m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty() && m_singleTrack->language() !=
"und") {
863 res.emplace(m_singleTrack->language());
882 const size_t trackCount = m_container->trackCount();
883 vector<string> parts;
884 parts.reserve(trackCount);
886 const string description(m_container->track(i)->description());
891 return joinStrings(parts,
" / ");
892 }
else if(m_singleTrack) {
893 return m_singleTrack->description();
939 m_id3v1Tag = make_unique<Id3v1Tag>();
941 return m_id3v1Tag.get();
960 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
961 if(i->get() == tag) {
962 m_id3v2Tags.erase(i);
1004 if(m_id3v2Tags.empty()) {
1005 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1007 return m_id3v2Tags.front().get();
1026 m_container->removeTag(tag);
1028 if(m_id3v1Tag.get() == tag) {
1031 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1032 if(i->get() == tag) {
1033 m_id3v2Tags.erase(i);
1048 m_container->removeAllTags();
1054 m_id3v2Tags.clear();
1062 if(m_container && m_container->chapterCount()) {
1065 switch(m_containerFormat) {
1079 if(m_container && m_container->attachmentCount()) {
1082 switch(m_containerFormat) {
1099 switch(m_containerFormat) {
1117 switch(m_containerFormat) {
1157 static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1183 vector<AbstractChapter *> res;
1185 const size_t count = m_container->chapterCount();
1187 for(
size_t i = 0; i != count; ++i) {
1188 res.push_back(m_container->chapter(i));
1201 vector<AbstractAttachment *> res;
1203 const size_t count = m_container->attachmentCount();
1205 for(
size_t i = 0; i != count; ++i) {
1206 res.push_back(m_container->attachment(i));
1219 if(m_container->hasNotifications()) {
1223 for(
const auto *track :
tracks()) {
1224 if(track->hasNotifications()) {
1228 for(
const auto *tag :
tags()) {
1229 if(tag->hasNotifications()) {
1233 for(
const auto *chapter :
chapters()) {
1234 if(chapter->hasNotifications()) {
1252 type |= m_container->worstNotificationType();
1257 for(
const auto *track :
tracks()) {
1258 type |= track->worstNotificationType();
1263 for(
const auto *tag :
tags()) {
1264 type |= tag->worstNotificationType();
1269 for(
const auto *chapter :
chapters()) {
1270 type |= chapter->worstNotificationType();
1288 switch(m_containerFormat) {
1292 if(!m_forceFullParse) {
1299 for(
const Notification ¬ification : m_container->notifications()) {
1300 if(find(notifications.cbegin(), notifications.cend(), notification) == notifications.cend()) {
1301 notifications.emplace_back(notification);
1306 notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());;
1309 for(
const auto *track :
tracks()) {
1310 notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1312 for(
const auto *tag :
tags()) {
1313 notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1315 for(
const auto *chapter :
chapters()) {
1316 notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1319 notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1347 m_containerOffset = 0;
1354 m_id3v2Tags.clear();
1355 m_actualId3v2TagOffsets.clear();
1356 m_actualExistingId3v1Tag =
false;
1357 m_container.reset();
1358 m_singleTrack.reset();
1379 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1382 auto isecond = begin + 1;
1383 if(isecond != end) {
1384 for(
auto i = isecond; i != end; ++i) {
1387 m_id3v2Tags.erase(isecond, end - 1);
1446 switch(m_containerFormat) {
1474 switch(m_containerFormat) {
1477 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1504 tags.push_back(m_id3v1Tag.get());
1506 for(
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1507 tags.push_back(tag.get());
1515 for(
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1516 tags.push_back(m_container->tag(i));
1528 || (m_container && m_container->tagCount())
1559 void MediaFileInfo::makeMp3File()
1561 static const string context(
"making MP3/FLAC file");
1564 if(m_actualExistingId3v1Tag) {
1571 stream().seekp(-128, ios_base::end);
1573 m_id3v1Tag->make(
stream());
1581 if(truncate(
path().c_str(),
size() - 128) == 0) {
1585 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1595 stream().seekp(0, ios_base::end);
1597 m_id3v1Tag->make(
stream());
1611 vector<Id3v2TagMaker> makers;
1612 makers.reserve(m_id3v2Tags.size());
1613 uint32 tagsSize = 0;
1614 for(
auto &tag : m_id3v2Tags) {
1616 makers.emplace_back(tag->prepareMaking());
1617 tagsSize += makers.back().requiredSize();
1626 uint32 streamOffset;
1627 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1628 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1629 uint32 startOfLastMetaDataBlock;
1633 startOfLastMetaDataBlock = flacStream->
makeHeader(flacMetaData);
1634 tagsSize += flacMetaData.tellp();
1638 streamOffset =
static_cast<uint32
>(m_containerOffset);
1642 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1644 if(!rewriteRequired) {
1647 padding = streamOffset - tagsSize;
1650 rewriteRequired =
true;
1653 if(makers.empty() && !flacStream) {
1659 rewriteRequired =
true;
1661 }
else if(rewriteRequired) {
1665 }
else if(makers.empty() && flacStream && padding && padding < 4) {
1669 rewriteRequired =
true;
1671 if(rewriteRequired && flacStream && makers.empty() && padding) {
1676 updateStatus(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1681 NativeFileStream &outputStream =
stream();
1682 NativeFileStream backupStream;
1684 if(rewriteRequired) {
1685 if(m_saveFilePath.empty()) {
1690 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1692 const char *what = catchIoFailure();
1694 throwIoFailure(what);
1700 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1701 backupStream.open(
path(), ios_base::in | ios_base::binary);
1702 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1704 const char *what = catchIoFailure();
1706 throwIoFailure(what);
1714 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1716 const char *what = catchIoFailure();
1718 throwIoFailure(what);
1724 if(!makers.empty()) {
1727 for(
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1728 i->make(outputStream, 0);
1731 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1735 if(padding && startOfLastMetaDataBlock) {
1737 flacMetaData.seekg(startOfLastMetaDataBlock);
1738 flacMetaData.seekp(startOfLastMetaDataBlock);
1739 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1740 flacMetaData.seekg(0);
1744 outputStream << flacMetaData.rdbuf();
1748 flacStream->
makePadding(outputStream, padding,
true);
1752 if(makers.empty() && !flacStream){
1754 for(; padding; --padding) {
1755 outputStream.put(0);
1761 uint64 mediaDataSize =
size() - streamOffset;
1762 if(m_actualExistingId3v1Tag) {
1763 mediaDataSize -= 128;
1766 if(rewriteRequired) {
1768 switch(m_containerFormat) {
1775 backupStream.seekg(streamOffset);
1781 outputStream.seekp(mediaDataSize, ios_base::cur);
1788 m_id3v1Tag->make(
stream());
1795 if(rewriteRequired) {
1801 m_saveFilePath.clear();
1804 outputStream.close();
1806 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1807 if(newSize <
size()) {
1810 outputStream.close();
1812 if(truncate(
path().c_str(), newSize) == 0) {
Contains utility classes helping to read and write streams.