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");
160 m_containerFormat = ContainerFormat::Unknown;
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) {
183 m_containerParsingStatus = ParsingStatus::NotSupported;
184 m_containerFormat = ContainerFormat::Unknown;
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)))) {
197 case ContainerFormat::Id2v2Tag:
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);
235 m_containerFormat = ContainerFormat::Matroska;
237 m_containerFormat = ContainerFormat::Webm;
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);
256 case ContainerFormat::Unknown:
259 if (
size() > 0x107) {
262 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
263 m_containerFormat = ContainerFormat::Tar;
273 if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
274 if (m_containerFormat == ContainerFormat::Unknown) {
275 m_containerParsingStatus = ParsingStatus::NotSupported;
277 m_containerParsingStatus = ParsingStatus::Ok;
301 static const string context(
"parsing tracks");
306 m_container->parseTracks(diag);
307 m_tracksParsingStatus = ParsingStatus::Ok;
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) {
341 m_tracksParsingStatus = ParsingStatus::Ok;
344 diag.emplace_back(DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
345 m_tracksParsingStatus = ParsingStatus::NotSupported;
347 diag.emplace_back(DiagLevel::Critical,
"Unable to parse tracks.", 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;
385 diag.emplace_back(DiagLevel::Critical,
"Unable to parse ID3v1 tag.", context);
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();
401 diag.emplace_back(DiagLevel::Critical,
"Unable to parse ID3v2 tag.", context);
403 m_id3v2Tags.emplace_back(id3v2Tag.release());
410 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
411 m_tagsParsingStatus = m_tracksParsingStatus;
414 }
else if (m_container) {
415 m_container->parseTags(diag);
421 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
422 m_tagsParsingStatus = ParsingStatus::Ok;
427 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
428 m_tagsParsingStatus = ParsingStatus::NotSupported;
430 diag.emplace_back(DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
433 diag.emplace_back(DiagLevel::Critical,
"Unable to parse tag.", context);
454 static const string context(
"parsing chapters");
461 m_container->parseChapters(diag);
462 m_chaptersParsingStatus = ParsingStatus::Ok;
464 m_chaptersParsingStatus = ParsingStatus::NotSupported;
465 diag.emplace_back(DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
468 diag.emplace_back(DiagLevel::Critical,
"Unable to parse chapters.", context);
489 static const string context(
"parsing attachments");
496 m_container->parseAttachments(diag);
497 m_attachmentsParsingStatus = ParsingStatus::Ok;
499 m_attachmentsParsingStatus = ParsingStatus::NotSupported;
500 diag.emplace_back(DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
503 diag.emplace_back(DiagLevel::Critical,
"Unable to parse attachments.", 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) {
578 if (!
hasAnyTag() && !(flags & TagCreationFlags::TreatUnknownFilesAsMp3Files)) {
592 if (flags & TagCreationFlags::Id3InitOnCreate) {
593 for (
const auto &id3v2Tag :
id3v2Tags()) {
605 if ((flags & TagCreationFlags::Id3InitOnCreate) &&
id3v1Tag()) {
606 id3v2Tag->insertValues(*
id3v1Tag(),
true);
611 if (flags & TagCreationFlags::MergeMultipleSuccessiveId3v2Tags) {
617 if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) &&
hasId3v2Tag()) {
623 if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) &&
hasId3v1Tag()) {
661 static const string context(
"making file");
662 diag.emplace_back(DiagLevel::Information,
"Changes are about to be applied.", context);
663 bool previousParsingSuccessful =
true;
665 case ParsingStatus::Ok:
666 case ParsingStatus::NotSupported:
669 previousParsingSuccessful =
false;
670 diag.emplace_back(DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
673 case ParsingStatus::Ok:
674 case ParsingStatus::NotSupported:
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);
691 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
692 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
694 m_container->makeFile(diag, progress);
703 makeMp3File(diag, progress);
727 MediaType mediaType = MediaType::Unknown;
729 switch (m_containerFormat) {
736 bool onlyOpus =
true, onlySpeex =
true;
744 if (track->format().general != GeneralMediaFormat::Speex) {
750 }
else if (onlySpeex) {
751 version =
static_cast<unsigned int>(GeneralMediaFormat::Speex);
755 case ContainerFormat::Matroska:
761 version = m_singleTrack->format().sub;
782 switch (m_containerFormat) {
785 case ContainerFormat::Matroska:
789 mediaType = MediaType::Unknown;
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) {
910 }
else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) &&
isLanguageDefined(m_singleTrack->language())) {
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);
1027 if (
tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
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) {
1130 case ContainerFormat::Matroska:
1131 case ContainerFormat::Webm:
1143 if (m_container && m_container->attachmentCount()) {
1146 switch (m_containerFormat) {
1147 case ContainerFormat::Matroska:
1148 case ContainerFormat::Webm:
1163 switch (m_containerFormat) {
1168 case ContainerFormat::Matroska:
1169 case ContainerFormat::Webm:
1181 switch (m_containerFormat) {
1184 case ContainerFormat::Matroska:
1189 case ContainerFormat::Webm:
1208 && m_container->tagCount() > 0
1222 if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1225 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
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));
1291 m_containerParsingStatus = ParsingStatus::NotParsedYet;
1292 m_containerFormat = ContainerFormat::Unknown;
1293 m_containerOffset = 0;
1295 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1296 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1297 m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1298 m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
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) {
1401 if (m_singleTrack) {
1421 switch (m_containerFormat) {
1424 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
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) {
1512 diag.emplace_back(DiagLevel::Information,
"Nothing to be changed.", context);
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 &) {
1534 diag.emplace_back(DiagLevel::Warning,
"Unable to write ID3v1 tag.", context);
1537 progress.
updateStep(
"Adding new ID3v1 tag ...");
1540 stream().seekp(0, ios_base::end);
1542 m_id3v1Tag->make(
stream(), diag);
1543 }
catch (
const Failure &) {
1544 diag.emplace_back(DiagLevel::Warning,
"Unable to write ID3v1 tag.", context);
1554 FlacStream *
const flacStream = (m_containerFormat ==
ContainerFormat::Flac ?
static_cast<FlacStream *
>(m_singleTrack.get()) :
nullptr);
1555 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1558 vector<Id3v2TagMaker> makers;
1559 makers.reserve(m_id3v2Tags.size());
1560 std::uint32_t tagsSize = 0;
1561 for (
auto &tag : m_id3v2Tags) {
1563 makers.emplace_back(tag->prepareMaking(diag));
1564 tagsSize += makers.back().requiredSize();
1565 }
catch (
const Failure &) {
1570 std::uint32_t streamOffset;
1571 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1572 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1573 std::streamoff startOfLastMetaDataBlock;
1576 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1577 tagsSize += flacMetaData.tellp();
1578 streamOffset = flacStream->streamOffset();
1581 streamOffset =
static_cast<std::uint32_t
>(m_containerOffset);
1585 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1587 if (!rewriteRequired) {
1590 padding = streamOffset - tagsSize;
1593 rewriteRequired =
true;
1596 if (makers.empty() && !flacStream) {
1602 rewriteRequired =
true;
1604 }
else if (rewriteRequired) {
1608 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1612 rewriteRequired =
true;
1614 if (rewriteRequired && flacStream && makers.empty() && padding) {
1619 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1624 NativeFileStream &outputStream =
stream();
1625 NativeFileStream backupStream;
1627 if (rewriteRequired) {
1628 if (m_saveFilePath.empty()) {
1634 }
catch (
const std::ios_base::failure &failure) {
1636 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1643 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1646 }
catch (
const std::ios_base::failure &failure) {
1647 diag.emplace_back(DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1657 }
catch (
const std::ios_base::failure &failure) {
1658 diag.emplace_back(DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1667 if (padding > numeric_limits<std::uint32_t>::max()) {
1668 padding = numeric_limits<std::uint32_t>::max();
1670 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1673 if (!makers.empty()) {
1675 progress.
updateStep(
"Writing ID3v2 tag ...");
1676 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1677 i->make(outputStream, 0, diag);
1680 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 :
static_cast<std::uint32_t
>(padding), diag);
1684 if (padding && startOfLastMetaDataBlock) {
1686 flacMetaData.seekg(startOfLastMetaDataBlock);
1687 flacMetaData.seekp(startOfLastMetaDataBlock);
1688 flacMetaData.put(
static_cast<std::uint8_t
>(flacMetaData.peek()) & (0x80u - 1));
1689 flacMetaData.seekg(0);
1693 outputStream << flacMetaData.rdbuf();
1697 flacStream->makePadding(outputStream,
static_cast<std::uint32_t
>(padding),
true, diag);
1701 if (makers.empty() && !flacStream) {
1703 for (; padding; --padding) {
1704 outputStream.put(0);
1710 std::uint64_t mediaDataSize =
size() - streamOffset;
1711 if (m_actualExistingId3v1Tag) {
1712 mediaDataSize -= 128;
1715 if (rewriteRequired) {
1717 switch (m_containerFormat) {
1719 progress.
updateStep(
"Writing MPEG audio frames ...");
1724 backupStream.seekg(
static_cast<streamoff
>(streamOffset));
1730 outputStream.seekp(
static_cast<std::streamoff
>(mediaDataSize), ios_base::cur);
1735 progress.
updateStep(
"Writing ID3v1 tag ...");
1737 m_id3v1Tag->make(
stream(), diag);
1738 }
catch (
const Failure &) {
1739 diag.emplace_back(DiagLevel::Warning,
"Unable to write ID3v1 tag.", context);
1744 if (rewriteRequired) {
1750 m_saveFilePath.clear();
1754 outputStream.close();
1756 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1757 if (newSize <
size()) {
1760 outputStream.close();
1765 diag.emplace_back(DiagLevel::Critical,
"Unable to truncate the file.", context);
1770 outputStream.flush();