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) {
645 static const string context(
"making file");
647 bool previousParsingSuccessful =
true;
653 previousParsingSuccessful =
false;
661 previousParsingSuccessful =
false;
664 if(!previousParsingSuccessful) {
675 m_container->forwardStatusUpdateCalls(
this);
679 m_container->makeFile();
714 switch(m_containerFormat) {
718 for(
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
730 version = m_singleTrack->format().sub;
752 switch(m_containerFormat) {
778 vector<AbstractTrack *> res;
780 size_t containerTrackCount = 0;
785 trackCount += (containerTrackCount = m_container->trackCount());
787 res.reserve(trackCount);
790 res.push_back(m_singleTrack.get());
792 for(
size_t i = 0; i != containerTrackCount; ++i) {
793 res.push_back(m_container->track(i));
809 if(m_singleTrack && m_singleTrack->mediaType() == type) {
811 }
else if(m_container) {
812 for(
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
813 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() && m_singleTrack->language() !=
"und") {
862 res.emplace(m_singleTrack->language());
881 const size_t trackCount = m_container->trackCount();
882 vector<string> parts;
883 parts.reserve(trackCount);
885 const string description(m_container->track(i)->description());
890 return joinStrings(parts,
" / ");
891 }
else if(m_singleTrack) {
892 return m_singleTrack->description();
938 m_id3v1Tag = make_unique<Id3v1Tag>();
940 return m_id3v1Tag.get();
959 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
960 if(i->get() == tag) {
961 m_id3v2Tags.erase(i);
1003 if(m_id3v2Tags.empty()) {
1004 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1006 return m_id3v2Tags.front().get();
1025 m_container->removeTag(tag);
1027 if(m_id3v1Tag.get() == tag) {
1030 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1031 if(i->get() == tag) {
1032 m_id3v2Tags.erase(i);
1047 m_container->removeAllTags();
1053 m_id3v2Tags.clear();
1061 if(m_container && m_container->chapterCount()) {
1064 switch(m_containerFormat) {
1078 if(m_container && m_container->attachmentCount()) {
1081 switch(m_containerFormat) {
1098 switch(m_containerFormat) {
1116 switch(m_containerFormat) {
1156 static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1182 vector<AbstractChapter *> res;
1184 const size_t count = m_container->chapterCount();
1186 for(
size_t i = 0; i != count; ++i) {
1187 res.push_back(m_container->chapter(i));
1200 vector<AbstractAttachment *> res;
1202 const size_t count = m_container->attachmentCount();
1204 for(
size_t i = 0; i != count; ++i) {
1205 res.push_back(m_container->attachment(i));
1218 if(m_container->hasNotifications()) {
1222 for(
const auto *track :
tracks()) {
1223 if(track->hasNotifications()) {
1227 for(
const auto *tag :
tags()) {
1228 if(tag->hasNotifications()) {
1232 for(
const auto *chapter :
chapters()) {
1233 if(chapter->hasNotifications()) {
1251 type |= m_container->worstNotificationType();
1256 for(
const auto *track :
tracks()) {
1257 type |= track->worstNotificationType();
1262 for(
const auto *tag :
tags()) {
1263 type |= tag->worstNotificationType();
1268 for(
const auto *chapter :
chapters()) {
1269 type |= chapter->worstNotificationType();
1287 switch(m_containerFormat) {
1291 if(!m_forceFullParse) {
1292 notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
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());
1350 m_containerOffset = 0;
1357 transferNotifications(*m_id3v1Tag);
1360 for(
auto &id3v2Tag : m_id3v2Tags) {
1361 transferNotifications(*id3v2Tag);
1363 m_id3v2Tags.clear();
1364 m_actualId3v2TagOffsets.clear();
1365 m_actualExistingId3v1Tag =
false;
1367 transferNotifications(*m_container);
1368 for(
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
1369 transferNotifications(*m_container->track(i));
1371 for(
size_t i = 0, count = m_container->tagCount(); i != count; ++i) {
1372 transferNotifications(*m_container->tag(i));
1374 for(
size_t i = 0, count = m_container->chapterCount(); i != count; ++i) {
1375 transferNotifications(*m_container->chapter(i));
1377 for(
size_t i = 0, count = m_container->attachmentCount(); i != count; ++i) {
1378 transferNotifications(*m_container->attachment(i));
1380 m_container.reset();
1383 transferNotifications(*m_singleTrack);
1384 m_singleTrack.reset();
1406 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1409 auto isecond = begin + 1;
1410 if(isecond != end) {
1411 for(
auto i = isecond; i != end; ++i) {
1414 m_id3v2Tags.erase(isecond, end - 1);
1473 switch(m_containerFormat) {
1501 switch(m_containerFormat) {
1504 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1531 tags.push_back(m_id3v1Tag.get());
1533 for(
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1534 tags.push_back(tag.get());
1542 for(
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1543 tags.push_back(m_container->tag(i));
1555 || (m_container && m_container->tagCount())
1586 void MediaFileInfo::makeMp3File()
1588 static const string context(
"making MP3/FLAC file");
1591 if(m_actualExistingId3v1Tag) {
1598 stream().seekp(-128, ios_base::end);
1600 m_id3v1Tag->make(
stream());
1608 if(truncate(
path().c_str(),
size() - 128) == 0) {
1612 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1622 stream().seekp(0, ios_base::end);
1624 m_id3v1Tag->make(
stream());
1638 vector<Id3v2TagMaker> makers;
1639 makers.reserve(m_id3v2Tags.size());
1640 uint32 tagsSize = 0;
1641 for(
auto &tag : m_id3v2Tags) {
1643 makers.emplace_back(tag->prepareMaking());
1644 tagsSize += makers.back().requiredSize();
1653 uint32 streamOffset;
1654 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1655 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1656 uint32 startOfLastMetaDataBlock;
1660 startOfLastMetaDataBlock = flacStream->
makeHeader(flacMetaData);
1661 tagsSize += flacMetaData.tellp();
1665 streamOffset =
static_cast<uint32
>(m_containerOffset);
1669 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1671 if(!rewriteRequired) {
1674 padding = streamOffset - tagsSize;
1677 rewriteRequired =
true;
1680 if(makers.empty() && !flacStream) {
1686 rewriteRequired =
true;
1688 }
else if(rewriteRequired) {
1692 }
else if(makers.empty() && flacStream && padding && padding < 4) {
1696 rewriteRequired =
true;
1698 if(rewriteRequired && flacStream && makers.empty() && padding) {
1703 updateStatus(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1708 NativeFileStream &outputStream =
stream();
1709 NativeFileStream backupStream;
1711 if(rewriteRequired) {
1712 if(m_saveFilePath.empty()) {
1717 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1719 const char *what = catchIoFailure();
1721 throwIoFailure(what);
1727 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1728 backupStream.open(
path(), ios_base::in | ios_base::binary);
1729 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1731 const char *what = catchIoFailure();
1733 throwIoFailure(what);
1741 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1743 const char *what = catchIoFailure();
1745 throwIoFailure(what);
1751 if(!makers.empty()) {
1754 for(
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1755 i->make(outputStream, 0);
1758 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1762 if(padding && startOfLastMetaDataBlock) {
1764 flacMetaData.seekg(startOfLastMetaDataBlock);
1765 flacMetaData.seekp(startOfLastMetaDataBlock);
1766 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1767 flacMetaData.seekg(0);
1771 outputStream << flacMetaData.rdbuf();
1775 flacStream->
makePadding(outputStream, padding,
true);
1779 if(makers.empty() && !flacStream){
1781 for(; padding; --padding) {
1782 outputStream.put(0);
1788 uint64 mediaDataSize =
size() - streamOffset;
1789 if(m_actualExistingId3v1Tag) {
1790 mediaDataSize -= 128;
1793 if(rewriteRequired) {
1795 switch(m_containerFormat) {
1802 backupStream.seekg(streamOffset);
1808 outputStream.seekp(mediaDataSize, ios_base::cur);
1815 m_id3v1Tag->make(
stream());
1822 if(rewriteRequired) {
1828 m_saveFilePath.clear();
1831 outputStream.close();
1833 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1834 if(newSize <
size()) {
1837 outputStream.close();
1839 if(truncate(
path().c_str(), newSize) == 0) {
Contains utility classes helping to read and write streams.