38 #include <c++utilities/chrono/timespan.h>
39 #include <c++utilities/conversion/stringconversion.h>
49 #include <system_error>
52 using namespace std::placeholders;
62 #ifdef FORCE_FULL_PARSE_DEFAULT
63 #define MEDIAINFO_CPP_FORCE_FULL_PARSE true
65 #define MEDIAINFO_CPP_FORCE_FULL_PARSE false
81 MediaFileInfo::MediaFileInfo()
84 , m_containerOffset(0)
85 , m_actualExistingId3v1Tag(false)
92 , m_preferredPadding(0)
96 , m_forceRewrite(true)
97 , m_forceTagPosition(true)
98 , m_forceIndexPosition(true)
111 , m_containerOffset(0)
112 , m_actualExistingId3v1Tag(false)
119 , m_preferredPadding(0)
123 , m_forceRewrite(true)
124 , m_forceTagPosition(true)
125 , m_forceIndexPosition(true)
158 static const string context(
"parsing file header");
164 m_containerOffset = 0;
168 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
169 startParsingSignature:
171 stream().seekg(m_containerOffset, ios_base::beg);
172 stream().read(buff,
sizeof(buff));
175 size_t bytesSkipped = 0;
176 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
178 if (bytesSkipped >= 4) {
179 m_containerOffset += bytesSkipped;
182 if ((m_paddingSize += bytesSkipped) >= 0x100u) {
189 goto startParsingSignature;
192 diag.emplace_back(
DiagLevel::Warning, argsToString(m_paddingSize,
" zero-bytes skipped at the beginning of the file."), context);
196 switch ((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
199 m_actualId3v2TagOffsets.push_back(m_containerOffset);
200 if (m_actualId3v2TagOffsets.size() == 2) {
201 diag.emplace_back(
DiagLevel::Warning,
"There is more than just one ID3v2 header at the beginning of the file.", context);
205 stream().seekg(m_containerOffset + 5, ios_base::beg);
209 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
210 if ((*buff) & 0x10) {
212 m_containerOffset += 10;
216 goto startParsingSignature;
221 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
223 static_cast<Mp4Container *>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
231 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
239 if (m_forceFullParse) {
242 container->validateElementStructure(diag, &m_paddingSize);
253 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
254 static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
259 if (
size() > 0x107) {
262 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
301 static const string context(
"parsing tracks");
306 m_container->parseTracks(diag);
312 switch (m_containerFormat) {
314 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
317 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
320 m_singleTrack = make_unique<IvfStream>(
stream(), m_containerOffset);
323 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
326 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
331 m_singleTrack->parseHeader(diag);
334 switch (m_containerFormat) {
336 m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->
paddingSize();
344 diag.emplace_back(
DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
372 static const string context(
"parsing tag");
376 m_id3v1Tag = make_unique<Id3v1Tag>();
378 stream().seekg(-128, ios_base::end);
379 m_id3v1Tag->parse(
stream(), diag);
380 m_actualExistingId3v1Tag =
true;
391 for (
const auto offset : m_actualId3v2TagOffsets) {
392 auto id3v2Tag = make_unique<Id3v2Tag>();
393 stream().seekg(offset, ios_base::beg);
395 id3v2Tag->parse(
stream(),
size() - static_cast<std::uint64_t>(offset), diag);
396 m_paddingSize += id3v2Tag->paddingSize();
403 m_id3v2Tags.emplace_back(id3v2Tag.release());
411 m_tagsParsingStatus = m_tracksParsingStatus;
414 }
else if (m_container) {
415 m_container->parseTags(diag);
430 diag.emplace_back(
DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
454 static const string context(
"parsing chapters");
461 m_container->parseChapters(diag);
465 diag.emplace_back(
DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
489 static const string context(
"parsing attachments");
496 m_container->parseAttachments(diag);
500 diag.emplace_back(
DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
542 const auto flags(settings.
flags);
543 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
544 auto targetsSupported =
false;
547 if (targetsRequired) {
549 if (m_container->tagCount()) {
551 targetsSupported = m_container->tag(0)->supportsTarget();
554 auto *
const tag = m_container->createTag();
555 if (tag && (targetsSupported = tag->supportsTarget())) {
556 tag->setTarget(requiredTargets.front());
559 if (targetsSupported) {
560 for (
const auto &target : requiredTargets) {
561 m_container->createTag(target);
566 m_container->createTag();
572 switch (m_containerFormat) {
593 for (
const auto &id3v2Tag :
id3v2Tags()) {
606 id3v2Tag->insertValues(*
id3v1Tag(),
true);
661 static const string context(
"making file");
663 bool previousParsingSuccessful =
true;
669 previousParsingSuccessful =
false;
670 diag.emplace_back(
DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
677 previousParsingSuccessful =
false;
678 diag.emplace_back(
DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
680 if (!previousParsingSuccessful) {
686 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
689 diag.emplace_back(
DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
694 m_container->makeFile(diag, progress);
703 makeMp3File(diag, progress);
729 switch (m_containerFormat) {
732 const auto &
tracks = static_cast<OggContainer *>(m_container.get())->
tracks();
736 bool onlyOpus =
true, onlySpeex =
true;
737 for (
const auto &track : static_cast<OggContainer *>(m_container.get())->
tracks()) {
750 }
else if (onlySpeex) {
761 version = m_singleTrack->format().sub;
782 switch (m_containerFormat) {
808 vector<AbstractTrack *> res;
810 size_t containerTrackCount = 0;
815 trackCount += (containerTrackCount = m_container->trackCount());
820 res.push_back(m_singleTrack.get());
822 for (
size_t i = 0; i != containerTrackCount; ++i) {
823 res.push_back(m_container->track(i));
841 if (m_singleTrack && m_singleTrack->mediaType() == type) {
843 }
else if (m_container) {
844 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
845 if (m_container->track(i)->mediaType() == type) {
865 return m_container->duration();
866 }
else if (m_singleTrack) {
867 return m_singleTrack->duration();
902 unordered_set<string> res;
904 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
911 res.emplace(m_singleTrack->language());
930 const size_t trackCount = m_container->trackCount();
931 vector<string> parts;
934 const string description(m_container->track(i)->description());
939 return joinStrings(parts,
" / ");
940 }
else if (m_singleTrack) {
941 return m_singleTrack->description();
988 m_id3v1Tag = make_unique<Id3v1Tag>();
990 return m_id3v1Tag.get();
1008 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1009 if (i->get() == tag) {
1010 m_id3v2Tags.erase(i);
1030 m_id3v2Tags.clear();
1051 if (m_id3v2Tags.empty()) {
1052 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1054 return m_id3v2Tags.front().get();
1078 return m_container->removeTag(tag);
1083 auto *
const flacStream(static_cast<FlacStream *>(m_singleTrack.get()));
1084 if (flacStream->vorbisComment() == tag) {
1085 return flacStream->removeVorbisComment();
1090 if (m_id3v1Tag.get() == tag) {
1094 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1095 if (i->get() == tag) {
1096 m_id3v2Tags.erase(i);
1112 m_container->removeAllTags();
1118 m_id3v2Tags.clear();
1126 if (m_container && m_container->chapterCount()) {
1129 switch (m_containerFormat) {
1143 if (m_container && m_container->attachmentCount()) {
1146 switch (m_containerFormat) {
1163 switch (m_containerFormat) {
1181 switch (m_containerFormat) {
1208 && m_container->tagCount() > 0
1209 ? static_cast<Mp4Container *>(m_container.get())->
tags().front().get()
1223 return static_cast<MatroskaContainer *>(m_container.get())->
tags();
1225 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1238 ? static_cast<OggContainer *>(m_container.get())->
tags().front().get()
1249 vector<AbstractChapter *> res;
1251 const size_t count = m_container->chapterCount();
1253 for (
size_t i = 0; i != count; ++i) {
1254 res.push_back(m_container->chapter(i));
1267 vector<AbstractAttachment *> res;
1269 const size_t count = m_container->attachmentCount();
1271 for (
size_t i = 0; i != count; ++i) {
1272 res.push_back(m_container->attachment(i));
1293 m_containerOffset = 0;
1300 m_id3v2Tags.clear();
1301 m_actualId3v2TagOffsets.clear();
1302 m_actualExistingId3v1Tag =
false;
1303 m_container.reset();
1304 m_singleTrack.reset();
1325 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1330 auto isecond = begin + 1;
1331 if (isecond == end) {
1334 for (
auto i = isecond; i != end; ++i) {
1337 m_id3v2Tags.erase(isecond, end - 1);
1394 switch (m_containerFormat) {
1397 return static_cast<OggContainer *>(m_container.get())->createTag(
TagTarget());
1401 if (m_singleTrack) {
1421 switch (m_containerFormat) {
1424 bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
1425 static_cast<OggContainer *>(m_container.get())->
removeAllTags();
1430 if (m_singleTrack) {
1450 tags.push_back(m_id3v1Tag.get());
1452 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1453 tags.push_back(tag.get());
1461 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1462 tags.push_back(m_container->tag(i));
1503 static const string context(
"making MP3/FLAC file");
1506 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1511 if (!m_actualExistingId3v1Tag) {
1515 progress.
updateStep(
"Removing ID3v1 tag ...");
1520 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1521 throw std::ios_base::failure(
"Unable to truncate file to remove ID3v1 tag.");
1526 if (m_actualExistingId3v1Tag) {
1527 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1530 stream().seekp(-128, ios_base::end);
1532 m_id3v1Tag->make(
stream(), diag);
1533 }
catch (
const Failure &) {
1537 progress.
updateStep(
"Adding new ID3v1 tag ...");
1540 stream().seekp(0, ios_base::end);
1542 m_id3v1Tag->make(
stream(), diag);
1543 }
catch (
const Failure &) {
1553 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1556 vector<Id3v2TagMaker> makers;
1557 makers.reserve(m_id3v2Tags.size());
1558 std::uint32_t tagsSize = 0;
1559 for (
auto &tag : m_id3v2Tags) {
1561 makers.emplace_back(tag->prepareMaking(diag));
1562 tagsSize += makers.back().requiredSize();
1563 }
catch (
const Failure &) {
1568 std::uint32_t streamOffset;
1569 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1570 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1571 std::streamoff startOfLastMetaDataBlock;
1574 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1575 tagsSize += flacMetaData.tellp();
1576 streamOffset = flacStream->streamOffset();
1579 streamOffset = static_cast<std::uint32_t>(m_containerOffset);
1583 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1585 if (!rewriteRequired) {
1588 padding = streamOffset - tagsSize;
1591 rewriteRequired =
true;
1594 if (makers.empty() && !flacStream) {
1600 rewriteRequired =
true;
1602 }
else if (rewriteRequired) {
1606 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1610 rewriteRequired =
true;
1612 if (rewriteRequired && flacStream && makers.empty() && padding) {
1617 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1622 NativeFileStream &outputStream =
stream();
1623 NativeFileStream backupStream;
1625 if (rewriteRequired) {
1626 if (m_saveFilePath.empty()) {
1632 }
catch (
const std::ios_base::failure &failure) {
1634 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1641 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1644 }
catch (
const std::ios_base::failure &failure) {
1645 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1655 }
catch (
const std::ios_base::failure &failure) {
1656 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1665 if (padding > numeric_limits<std::uint32_t>::max()) {
1666 padding = numeric_limits<std::uint32_t>::max();
1668 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1671 if (!makers.empty()) {
1673 progress.
updateStep(
"Writing ID3v2 tag ...");
1674 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1675 i->make(outputStream, 0, diag);
1678 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : static_cast<std::uint32_t>(padding), diag);
1682 if (padding && startOfLastMetaDataBlock) {
1684 flacMetaData.seekg(startOfLastMetaDataBlock);
1685 flacMetaData.seekp(startOfLastMetaDataBlock);
1686 flacMetaData.put(static_cast<std::uint8_t>(flacMetaData.peek()) & (0x80u - 1));
1687 flacMetaData.seekg(0);
1691 outputStream << flacMetaData.rdbuf();
1695 flacStream->makePadding(outputStream, static_cast<std::uint32_t>(padding),
true, diag);
1699 if (makers.empty() && !flacStream) {
1701 for (; padding; --padding) {
1702 outputStream.put(0);
1708 std::uint64_t mediaDataSize =
size() - streamOffset;
1709 if (m_actualExistingId3v1Tag) {
1710 mediaDataSize -= 128;
1713 if (rewriteRequired) {
1715 switch (m_containerFormat) {
1717 progress.
updateStep(
"Writing MPEG audio frames ...");
1722 backupStream.seekg(static_cast<streamoff>(streamOffset));
1728 outputStream.seekp(static_cast<std::streamoff>(mediaDataSize), ios_base::cur);
1733 progress.
updateStep(
"Writing ID3v1 tag ...");
1735 m_id3v1Tag->make(
stream(), diag);
1736 }
catch (
const Failure &) {
1742 if (rewriteRequired) {
1748 m_saveFilePath.clear();
1751 outputStream.close();
1753 const auto newSize = static_cast<std::uint64_t>(outputStream.tellp());
1754 if (newSize <
size()) {
1757 outputStream.close();