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) {
567 if(id3InitOnCreate) {
568 for(
const auto &id3v2Tag :
id3v2Tags()) {
589 if(mergeMultipleSuccessiveId3v2Tags) {
610 }
else if(!keepExistingId3v2version) {
613 tag->setVersion(id3v2MajorVersion, 0);
617 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) {
722 bool onlyOpus =
true, onlySpeex =
true;
723 for(
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
736 }
else if(onlySpeex) {
746 version = m_singleTrack->format().sub;
768 switch(m_containerFormat) {
794 vector<AbstractTrack *> res;
796 size_t containerTrackCount = 0;
801 trackCount += (containerTrackCount = m_container->trackCount());
803 res.reserve(trackCount);
806 res.push_back(m_singleTrack.get());
808 for(
size_t i = 0; i != containerTrackCount; ++i) {
809 res.push_back(m_container->track(i));
827 if(m_singleTrack && m_singleTrack->mediaType() == type) {
829 }
else if(m_container) {
830 for(
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
831 if(m_container->track(i)->mediaType() == type) {
851 return m_container->duration();
852 }
else if(m_singleTrack) {
853 return m_singleTrack->duration();
870 unordered_set<string> res;
872 for(
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
878 }
else if(m_singleTrack && (type ==
MediaType::Unknown || m_singleTrack->mediaType() == type) && !m_singleTrack->language().empty() && m_singleTrack->language() !=
"und") {
879 res.emplace(m_singleTrack->language());
898 const size_t trackCount = m_container->trackCount();
899 vector<string> parts;
900 parts.reserve(trackCount);
902 const string description(m_container->track(i)->description());
907 return joinStrings(parts,
" / ");
908 }
else if(m_singleTrack) {
909 return m_singleTrack->description();
956 m_id3v1Tag = make_unique<Id3v1Tag>();
958 return m_id3v1Tag.get();
979 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
980 if(i->get() == tag) {
981 m_id3v2Tags.erase(i);
1001 m_id3v2Tags.clear();
1022 if(m_id3v2Tags.empty()) {
1023 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1025 return m_id3v2Tags.front().get();
1046 m_container->removeTag(tag);
1048 if(m_id3v1Tag.get() == tag) {
1051 for(
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1052 if(i->get() == tag) {
1053 m_id3v2Tags.erase(i);
1067 m_container->removeAllTags();
1073 m_id3v2Tags.clear();
1081 if(m_container && m_container->chapterCount()) {
1084 switch(m_containerFormat) {
1098 if(m_container && m_container->attachmentCount()) {
1101 switch(m_containerFormat) {
1118 switch(m_containerFormat) {
1136 switch(m_containerFormat) {
1177 static const std::vector<std::unique_ptr<MatroskaTag> > empty;
1203 vector<AbstractChapter *> res;
1205 const size_t count = m_container->chapterCount();
1207 for(
size_t i = 0; i != count; ++i) {
1208 res.push_back(m_container->chapter(i));
1221 vector<AbstractAttachment *> res;
1223 const size_t count = m_container->attachmentCount();
1225 for(
size_t i = 0; i != count; ++i) {
1226 res.push_back(m_container->attachment(i));
1238 if(m_container && m_container->hasNotifications()) {
1241 for(
const auto *track :
tracks()) {
1242 if(track->hasNotifications()) {
1246 for(
const auto *tag :
tags()) {
1247 if(tag->hasNotifications()) {
1251 for(
const auto *chapter :
chapters()) {
1252 if(chapter->hasNotifications()) {
1270 type |= m_container->worstNotificationType();
1275 for(
const auto *track :
tracks()) {
1276 type |= track->worstNotificationType();
1281 for(
const auto *tag :
tags()) {
1282 type |= tag->worstNotificationType();
1287 for(
const auto *chapter :
chapters()) {
1288 type |= chapter->worstNotificationType();
1306 switch(m_containerFormat) {
1310 if(!m_forceFullParse) {
1311 notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());
1318 for(
const Notification ¬ification : m_container->notifications()) {
1319 if(find(notifications.cbegin(), notifications.cend(), notification) == notifications.cend()) {
1320 notifications.emplace_back(notification);
1325 notifications.insert(notifications.end(), m_container->notifications().cbegin(), m_container->notifications().cend());;
1328 for(
const auto *track :
tracks()) {
1329 notifications.insert(notifications.end(), track->notifications().cbegin(), track->notifications().cend());
1331 for(
const auto *tag :
tags()) {
1332 notifications.insert(notifications.end(), tag->notifications().cbegin(), tag->notifications().cend());
1334 for(
const auto *chapter :
chapters()) {
1335 notifications.insert(notifications.end(), chapter->notifications().cbegin(), chapter->notifications().cend());
1338 notifications.insert(notifications.end(), attachment->notifications().cbegin(), attachment->notifications().cend());
1369 m_containerOffset = 0;
1376 transferNotifications(*m_id3v1Tag);
1379 for(
auto &id3v2Tag : m_id3v2Tags) {
1380 transferNotifications(*id3v2Tag);
1382 m_id3v2Tags.clear();
1383 m_actualId3v2TagOffsets.clear();
1384 m_actualExistingId3v1Tag =
false;
1386 transferNotifications(*m_container);
1387 for(
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
1388 transferNotifications(*m_container->track(i));
1390 for(
size_t i = 0, count = m_container->tagCount(); i != count; ++i) {
1391 transferNotifications(*m_container->tag(i));
1393 for(
size_t i = 0, count = m_container->chapterCount(); i != count; ++i) {
1394 transferNotifications(*m_container->chapter(i));
1396 for(
size_t i = 0, count = m_container->attachmentCount(); i != count; ++i) {
1397 transferNotifications(*m_container->attachment(i));
1399 m_container.reset();
1402 transferNotifications(*m_singleTrack);
1403 m_singleTrack.reset();
1425 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1430 auto isecond = begin + 1;
1431 if(isecond == end) {
1434 for(
auto i = isecond; i != end; ++i) {
1437 m_id3v2Tags.erase(isecond, end - 1);
1494 switch(m_containerFormat) {
1522 switch(m_containerFormat) {
1525 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1552 tags.push_back(m_id3v1Tag.get());
1554 for(
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1555 tags.push_back(tag.get());
1563 for(
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1564 tags.push_back(m_container->tag(i));
1576 || (m_container && m_container->tagCount())
1607 void MediaFileInfo::makeMp3File()
1609 static const string context(
"making MP3/FLAC file");
1612 if(m_actualExistingId3v1Tag) {
1619 stream().seekp(-128, ios_base::end);
1621 m_id3v1Tag->make(
stream());
1629 if(truncate(
path().c_str(),
size() - 128) == 0) {
1633 throwIoFailure(
"Unable to truncate file to remove ID3v1 tag.");
1643 stream().seekp(0, ios_base::end);
1645 m_id3v1Tag->make(
stream());
1659 vector<Id3v2TagMaker> makers;
1660 makers.reserve(m_id3v2Tags.size());
1661 uint32 tagsSize = 0;
1662 for(
auto &tag : m_id3v2Tags) {
1664 makers.emplace_back(tag->prepareMaking());
1665 tagsSize += makers.back().requiredSize();
1674 uint32 streamOffset;
1675 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1676 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1677 uint32 startOfLastMetaDataBlock;
1681 startOfLastMetaDataBlock = flacStream->
makeHeader(flacMetaData);
1682 tagsSize += flacMetaData.tellp();
1686 streamOffset =
static_cast<uint32
>(m_containerOffset);
1690 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1692 if(!rewriteRequired) {
1695 padding = streamOffset - tagsSize;
1698 rewriteRequired =
true;
1701 if(makers.empty() && !flacStream) {
1707 rewriteRequired =
true;
1709 }
else if(rewriteRequired) {
1713 }
else if(makers.empty() && flacStream && padding && padding < 4) {
1717 rewriteRequired =
true;
1719 if(rewriteRequired && flacStream && makers.empty() && padding) {
1724 updateStatus(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1729 NativeFileStream &outputStream =
stream();
1730 NativeFileStream backupStream;
1732 if(rewriteRequired) {
1733 if(m_saveFilePath.empty()) {
1738 outputStream.open(
path(), ios_base::out | ios_base::binary | ios_base::trunc);
1740 const char *what = catchIoFailure();
1742 throwIoFailure(what);
1748 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1749 backupStream.open(
path(), ios_base::in | ios_base::binary);
1750 outputStream.open(m_saveFilePath, ios_base::out | ios_base::binary | ios_base::trunc);
1752 const char *what = catchIoFailure();
1754 throwIoFailure(what);
1762 outputStream.open(
path(), ios_base::in | ios_base::out | ios_base::binary);
1764 const char *what = catchIoFailure();
1766 throwIoFailure(what);
1772 if(!makers.empty()) {
1775 for(
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1776 i->make(outputStream, 0);
1779 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
1783 if(padding && startOfLastMetaDataBlock) {
1785 flacMetaData.seekg(startOfLastMetaDataBlock);
1786 flacMetaData.seekp(startOfLastMetaDataBlock);
1787 flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
1788 flacMetaData.seekg(0);
1792 outputStream << flacMetaData.rdbuf();
1796 flacStream->
makePadding(outputStream, padding,
true);
1800 if(makers.empty() && !flacStream){
1802 for(; padding; --padding) {
1803 outputStream.put(0);
1809 uint64 mediaDataSize =
size() - streamOffset;
1810 if(m_actualExistingId3v1Tag) {
1811 mediaDataSize -= 128;
1814 if(rewriteRequired) {
1816 switch(m_containerFormat) {
1823 backupStream.seekg(streamOffset);
1829 outputStream.seekp(mediaDataSize, ios_base::cur);
1836 m_id3v1Tag->make(
stream());
1843 if(rewriteRequired) {
1849 m_saveFilePath.clear();
1852 outputStream.close();
1854 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
1855 if(newSize <
size()) {
1858 outputStream.close();
1860 if(truncate(
path().c_str(), newSize) == 0) {
Contains utility classes helping to read and write streams.