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;
165 size_t bytesSkippedBeforeContainer = 0;
169 const char *
const buffEnd = buff +
sizeof(buff), *buffOffset;
170 startParsingSignature:
172 stream().seekg(m_containerOffset, ios_base::beg);
173 stream().read(buff,
sizeof(buff));
180 size_t bytesSkipped = 0;
181 for (buffOffset = buff; buffOffset != buffEnd && !(*buffOffset); ++buffOffset, ++bytesSkipped)
183 if (bytesSkipped >= 4) {
185 m_containerOffset += bytesSkipped;
186 m_paddingSize += bytesSkipped;
189 if ((bytesSkippedBeforeContainer += bytesSkipped) >= 0x800u) {
190 m_containerParsingStatus = ParsingStatus::NotSupported;
191 m_containerFormat = ContainerFormat::Unknown;
196 goto startParsingSignature;
200 switch ((m_containerFormat =
parseSignature(buff,
sizeof(buff)))) {
201 case ContainerFormat::Id2v2Tag:
203 m_actualId3v2TagOffsets.push_back(m_containerOffset);
204 if (m_actualId3v2TagOffsets.size() == 2) {
205 diag.emplace_back(DiagLevel::Warning,
"There is more than just one ID3v2 header at the beginning of the file.", context);
209 stream().seekg(m_containerOffset + 5, ios_base::beg);
213 m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
214 if ((*buff) & 0x10) {
216 m_containerOffset += 10;
220 goto startParsingSignature;
225 m_container = make_unique<Mp4Container>(*
this, m_containerOffset);
227 static_cast<Mp4Container *
>(m_container.get())->validateElementStructure(diag, &m_paddingSize);
235 auto container = make_unique<MatroskaContainer>(*
this, m_containerOffset);
239 m_containerFormat = ContainerFormat::Matroska;
241 m_containerFormat = ContainerFormat::Webm;
243 if (m_forceFullParse) {
246 container->validateElementStructure(diag, &m_paddingSize);
257 m_container = make_unique<OggContainer>(*
this, m_containerOffset);
258 static_cast<OggContainer *
>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
260 case ContainerFormat::Unknown:
263 if (
size() > 0x107) {
266 if (buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
267 m_containerFormat = ContainerFormat::Tar;
280 if (bytesSkippedBeforeContainer) {
281 diag.emplace_back(DiagLevel::Warning, argsToString(bytesSkippedBeforeContainer,
" bytes of junk skipped"), context);
285 if (m_containerParsingStatus == ParsingStatus::NotParsedYet) {
286 if (m_containerFormat == ContainerFormat::Unknown) {
287 m_containerParsingStatus = ParsingStatus::NotSupported;
289 m_containerParsingStatus = ParsingStatus::Ok;
313 static const string context(
"parsing tracks");
318 m_container->parseTracks(diag);
319 m_tracksParsingStatus = ParsingStatus::Ok;
324 switch (m_containerFormat) {
326 m_singleTrack = make_unique<AdtsStream>(
stream(), m_containerOffset);
329 m_singleTrack = make_unique<FlacStream>(*
this, m_containerOffset);
332 m_singleTrack = make_unique<IvfStream>(
stream(), m_containerOffset);
335 m_singleTrack = make_unique<MpegAudioFrameStream>(
stream(), m_containerOffset);
338 m_singleTrack = make_unique<WaveAudioStream>(
stream(), m_containerOffset);
343 m_singleTrack->parseHeader(diag);
346 switch (m_containerFormat) {
353 m_tracksParsingStatus = ParsingStatus::Ok;
356 diag.emplace_back(DiagLevel::Information,
"Parsing tracks is not implemented for the container format of the file.", context);
357 m_tracksParsingStatus = ParsingStatus::NotSupported;
359 diag.emplace_back(DiagLevel::Critical,
"Unable to parse tracks.", context);
384 static const string context(
"parsing tag");
388 m_id3v1Tag = make_unique<Id3v1Tag>();
390 stream().seekg(-128, ios_base::end);
391 m_id3v1Tag->parse(
stream(), diag);
392 m_actualExistingId3v1Tag =
true;
397 diag.emplace_back(DiagLevel::Critical,
"Unable to parse ID3v1 tag.", context);
403 for (
const auto offset : m_actualId3v2TagOffsets) {
404 auto id3v2Tag = make_unique<Id3v2Tag>();
405 stream().seekg(offset, ios_base::beg);
407 id3v2Tag->parse(
stream(),
size() -
static_cast<std::uint64_t
>(offset), diag);
408 m_paddingSize += id3v2Tag->paddingSize();
413 diag.emplace_back(DiagLevel::Critical,
"Unable to parse ID3v2 tag.", context);
415 m_id3v2Tags.emplace_back(id3v2Tag.release());
422 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
423 m_tagsParsingStatus = m_tracksParsingStatus;
426 }
else if (m_container) {
427 m_container->parseTags(diag);
433 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
434 m_tagsParsingStatus = ParsingStatus::Ok;
439 if (m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
440 m_tagsParsingStatus = ParsingStatus::NotSupported;
442 diag.emplace_back(DiagLevel::Information,
"Parsing tags is not implemented for the container format of the file.", context);
445 diag.emplace_back(DiagLevel::Critical,
"Unable to parse tag.", context);
466 static const string context(
"parsing chapters");
473 m_container->parseChapters(diag);
474 m_chaptersParsingStatus = ParsingStatus::Ok;
476 m_chaptersParsingStatus = ParsingStatus::NotSupported;
477 diag.emplace_back(DiagLevel::Information,
"Parsing chapters is not implemented for the container format of the file.", context);
480 diag.emplace_back(DiagLevel::Critical,
"Unable to parse chapters.", context);
501 static const string context(
"parsing attachments");
508 m_container->parseAttachments(diag);
509 m_attachmentsParsingStatus = ParsingStatus::Ok;
511 m_attachmentsParsingStatus = ParsingStatus::NotSupported;
512 diag.emplace_back(DiagLevel::Information,
"Parsing attachments is not implemented for the container format of the file.", context);
515 diag.emplace_back(DiagLevel::Critical,
"Unable to parse attachments.", context);
554 const auto flags(settings.
flags);
555 const auto targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 || !requiredTargets.front().isEmpty());
556 auto targetsSupported =
false;
559 if (targetsRequired) {
561 if (m_container->tagCount()) {
563 targetsSupported = m_container->tag(0)->supportsTarget();
566 auto *
const tag = m_container->createTag();
567 if (tag && (targetsSupported = tag->supportsTarget())) {
568 tag->setTarget(requiredTargets.front());
571 if (targetsSupported) {
572 for (
const auto &target : requiredTargets) {
573 m_container->createTag(target);
578 m_container->createTag();
584 switch (m_containerFormat) {
590 if (!
hasAnyTag() && !(flags & TagCreationFlags::TreatUnknownFilesAsMp3Files)) {
604 if (flags & TagCreationFlags::Id3InitOnCreate) {
605 for (
const auto &id3v2Tag :
id3v2Tags()) {
617 if ((flags & TagCreationFlags::Id3InitOnCreate) &&
id3v1Tag()) {
618 id3v2Tag->insertValues(*
id3v1Tag(),
true);
623 if (flags & TagCreationFlags::MergeMultipleSuccessiveId3v2Tags) {
629 if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) &&
hasId3v2Tag()) {
635 if ((flags & TagCreationFlags::Id3TransferValuesOnRemoval) &&
hasId3v1Tag()) {
673 static const string context(
"making file");
674 diag.emplace_back(DiagLevel::Information,
"Changes are about to be applied.", context);
675 bool previousParsingSuccessful =
true;
677 case ParsingStatus::Ok:
678 case ParsingStatus::NotSupported:
681 previousParsingSuccessful =
false;
682 diag.emplace_back(DiagLevel::Critical,
"Tags have to be parsed without critical errors before changes can be applied.", context);
685 case ParsingStatus::Ok:
686 case ParsingStatus::NotSupported:
689 previousParsingSuccessful =
false;
690 diag.emplace_back(DiagLevel::Critical,
"Tracks have to be parsed without critical errors before changes can be applied.", context);
692 if (!previousParsingSuccessful) {
698 diag.emplace_back(DiagLevel::Warning,
"Assigned ID3v1 tag can't be attached and will be ignored.", context);
701 diag.emplace_back(DiagLevel::Warning,
"Assigned ID3v2 tag can't be attached and will be ignored.", context);
703 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
704 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
706 m_container->makeFile(diag, progress);
715 makeMp3File(diag, progress);
739 MediaType mediaType = MediaType::Unknown;
741 switch (m_containerFormat) {
748 bool onlyOpus =
true, onlySpeex =
true;
756 if (track->format().general != GeneralMediaFormat::Speex) {
762 }
else if (onlySpeex) {
763 version =
static_cast<unsigned int>(GeneralMediaFormat::Speex);
767 case ContainerFormat::Matroska:
773 version = m_singleTrack->format().sub;
794 switch (m_containerFormat) {
797 case ContainerFormat::Matroska:
801 mediaType = MediaType::Unknown;
820 vector<AbstractTrack *> res;
822 size_t containerTrackCount = 0;
827 trackCount += (containerTrackCount = m_container->trackCount());
832 res.push_back(m_singleTrack.get());
834 for (
size_t i = 0; i != containerTrackCount; ++i) {
835 res.push_back(m_container->track(i));
853 if (m_singleTrack && m_singleTrack->mediaType() == type) {
855 }
else if (m_container) {
856 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
857 if (m_container->track(i)->mediaType() == type) {
877 return m_container->duration();
878 }
else if (m_singleTrack) {
879 return m_singleTrack->duration();
914 unordered_set<string> res;
916 for (
size_t i = 0, count = m_container->trackCount(); i != count; ++i) {
922 }
else if (m_singleTrack && (type == MediaType::Unknown || m_singleTrack->mediaType() == type) &&
isLanguageDefined(m_singleTrack->language())) {
923 res.emplace(m_singleTrack->language());
942 const size_t trackCount = m_container->trackCount();
943 vector<string> parts;
946 const string description(m_container->track(i)->description());
951 return joinStrings(parts,
" / ");
952 }
else if (m_singleTrack) {
953 return m_singleTrack->description();
1000 m_id3v1Tag = make_unique<Id3v1Tag>();
1002 return m_id3v1Tag.get();
1020 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1021 if (i->get() == tag) {
1022 m_id3v2Tags.erase(i);
1039 if (
tagsParsingStatus() == ParsingStatus::NotParsedYet || m_id3v2Tags.empty()) {
1042 m_id3v2Tags.clear();
1063 if (m_id3v2Tags.empty()) {
1064 m_id3v2Tags.emplace_back(make_unique<Id3v2Tag>());
1066 return m_id3v2Tags.front().get();
1090 return m_container->removeTag(tag);
1095 auto *
const flacStream(
static_cast<FlacStream *
>(m_singleTrack.get()));
1096 if (flacStream->vorbisComment() == tag) {
1097 return flacStream->removeVorbisComment();
1102 if (m_id3v1Tag.get() == tag) {
1106 for (
auto i = m_id3v2Tags.begin(), end = m_id3v2Tags.end(); i != end; ++i) {
1107 if (i->get() == tag) {
1108 m_id3v2Tags.erase(i);
1124 m_container->removeAllTags();
1130 m_id3v2Tags.clear();
1138 if (m_container && m_container->chapterCount()) {
1141 switch (m_containerFormat) {
1142 case ContainerFormat::Matroska:
1143 case ContainerFormat::Webm:
1155 if (m_container && m_container->attachmentCount()) {
1158 switch (m_containerFormat) {
1159 case ContainerFormat::Matroska:
1160 case ContainerFormat::Webm:
1175 switch (m_containerFormat) {
1180 case ContainerFormat::Matroska:
1181 case ContainerFormat::Webm:
1193 switch (m_containerFormat) {
1196 case ContainerFormat::Matroska:
1201 case ContainerFormat::Webm:
1220 && m_container->tagCount() > 0
1234 if (m_containerFormat == ContainerFormat::Matroska && m_container) {
1237 static const std::vector<std::unique_ptr<MatroskaTag>> empty;
1261 vector<AbstractChapter *> res;
1263 const size_t count = m_container->chapterCount();
1265 for (
size_t i = 0; i != count; ++i) {
1266 res.push_back(m_container->chapter(i));
1279 vector<AbstractAttachment *> res;
1281 const size_t count = m_container->attachmentCount();
1283 for (
size_t i = 0; i != count; ++i) {
1284 res.push_back(m_container->attachment(i));
1303 m_containerParsingStatus = ParsingStatus::NotParsedYet;
1304 m_containerFormat = ContainerFormat::Unknown;
1305 m_containerOffset = 0;
1307 m_tracksParsingStatus = ParsingStatus::NotParsedYet;
1308 m_tagsParsingStatus = ParsingStatus::NotParsedYet;
1309 m_chaptersParsingStatus = ParsingStatus::NotParsedYet;
1310 m_attachmentsParsingStatus = ParsingStatus::NotParsedYet;
1312 m_id3v2Tags.clear();
1313 m_actualId3v2TagOffsets.clear();
1314 m_actualExistingId3v1Tag =
false;
1315 m_container.reset();
1316 m_singleTrack.reset();
1337 auto begin = m_id3v2Tags.begin(), end = m_id3v2Tags.end();
1342 auto isecond = begin + 1;
1343 if (isecond == end) {
1346 for (
auto i = isecond; i != end; ++i) {
1349 m_id3v2Tags.erase(isecond, end - 1);
1406 switch (m_containerFormat) {
1413 if (m_singleTrack) {
1433 switch (m_containerFormat) {
1436 bool hadTags =
static_cast<OggContainer *
>(m_container.get())->tagCount();
1442 if (m_singleTrack) {
1462 tags.push_back(m_id3v1Tag.get());
1464 for (
const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
1465 tags.push_back(tag.get());
1473 for (
size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
1474 tags.push_back(m_container->tag(i));
1515 static const string context(
"making MP3/FLAC file");
1518 if (!
isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()
1523 if (!m_actualExistingId3v1Tag) {
1524 diag.emplace_back(DiagLevel::Information,
"Nothing to be changed.", context);
1527 progress.
updateStep(
"Removing ID3v1 tag ...");
1532 diag.emplace_back(DiagLevel::Critical,
"Unable to truncate file to remove ID3v1 tag.", context);
1533 throw std::ios_base::failure(
"Unable to truncate file to remove ID3v1 tag.");
1538 if (m_actualExistingId3v1Tag) {
1539 progress.
updateStep(
"Updating existing ID3v1 tag ...");
1542 stream().seekp(-128, ios_base::end);
1544 m_id3v1Tag->make(
stream(), diag);
1545 }
catch (
const Failure &) {
1546 diag.emplace_back(DiagLevel::Warning,
"Unable to write ID3v1 tag.", context);
1549 progress.
updateStep(
"Adding new ID3v1 tag ...");
1552 stream().seekp(0, ios_base::end);
1554 m_id3v1Tag->make(
stream(), diag);
1555 }
catch (
const Failure &) {
1556 diag.emplace_back(DiagLevel::Warning,
"Unable to write ID3v1 tag.", context);
1566 FlacStream *
const flacStream = (m_containerFormat ==
ContainerFormat::Flac ?
static_cast<FlacStream *
>(m_singleTrack.get()) :
nullptr);
1567 progress.
updateStep(flacStream ?
"Updating FLAC tags ..." :
"Updating ID3v2 tags ...");
1570 vector<Id3v2TagMaker> makers;
1571 makers.reserve(m_id3v2Tags.size());
1572 std::uint32_t tagsSize = 0;
1573 for (
auto &tag : m_id3v2Tags) {
1575 makers.emplace_back(tag->prepareMaking(diag));
1576 tagsSize += makers.back().requiredSize();
1577 }
catch (
const Failure &) {
1582 std::uint32_t streamOffset;
1583 stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
1584 flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
1585 std::streamoff startOfLastMetaDataBlock;
1588 startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
1589 tagsSize += flacMetaData.tellp();
1590 streamOffset = flacStream->streamOffset();
1593 streamOffset =
static_cast<std::uint32_t
>(m_containerOffset);
1597 bool rewriteRequired =
isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
1599 if (!rewriteRequired) {
1602 padding = streamOffset - tagsSize;
1605 rewriteRequired =
true;
1608 if (makers.empty() && !flacStream) {
1614 rewriteRequired =
true;
1616 }
else if (rewriteRequired) {
1620 }
else if (makers.empty() && flacStream && padding && padding < 4) {
1624 rewriteRequired =
true;
1626 if (rewriteRequired && flacStream && makers.empty() && padding) {
1631 progress.
updateStep(rewriteRequired ?
"Preparing streams for rewriting ..." :
"Preparing streams for updating ...");
1636 NativeFileStream &outputStream =
stream();
1637 NativeFileStream backupStream;
1639 if (rewriteRequired) {
1640 if (m_saveFilePath.empty()) {
1646 }
catch (
const std::ios_base::failure &failure) {
1648 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
1655 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1658 }
catch (
const std::ios_base::failure &failure) {
1659 diag.emplace_back(DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
1669 }
catch (
const std::ios_base::failure &failure) {
1670 diag.emplace_back(DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
1679 if (padding > numeric_limits<std::uint32_t>::max()) {
1680 padding = numeric_limits<std::uint32_t>::max();
1682 DiagLevel::Critical, argsToString(
"Preferred padding is not supported. Setting preferred padding to ", padding,
'.'), context);
1685 if (!makers.empty()) {
1687 progress.
updateStep(
"Writing ID3v2 tag ...");
1688 for (
auto i = makers.begin(), end = makers.end() - 1; i != end; ++i) {
1689 i->make(outputStream, 0, diag);
1692 makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 :
static_cast<std::uint32_t
>(padding), diag);
1696 if (padding && startOfLastMetaDataBlock) {
1698 flacMetaData.seekg(startOfLastMetaDataBlock);
1699 flacMetaData.seekp(startOfLastMetaDataBlock);
1700 flacMetaData.put(
static_cast<std::uint8_t
>(flacMetaData.peek()) & (0x80u - 1));
1701 flacMetaData.seekg(0);
1705 outputStream << flacMetaData.rdbuf();
1709 flacStream->makePadding(outputStream,
static_cast<std::uint32_t
>(padding),
true, diag);
1713 if (makers.empty() && !flacStream) {
1715 for (; padding; --padding) {
1716 outputStream.put(0);
1722 std::uint64_t mediaDataSize =
size() - streamOffset;
1723 if (m_actualExistingId3v1Tag) {
1724 mediaDataSize -= 128;
1727 if (rewriteRequired) {
1729 switch (m_containerFormat) {
1731 progress.
updateStep(
"Writing MPEG audio frames ...");
1736 backupStream.seekg(
static_cast<streamoff
>(streamOffset));
1742 outputStream.seekp(
static_cast<std::streamoff
>(mediaDataSize), ios_base::cur);
1747 progress.
updateStep(
"Writing ID3v1 tag ...");
1749 m_id3v1Tag->make(
stream(), diag);
1750 }
catch (
const Failure &) {
1751 diag.emplace_back(DiagLevel::Warning,
"Unable to write ID3v1 tag.", context);
1756 if (rewriteRequired) {
1762 m_saveFilePath.clear();
1766 outputStream.close();
1768 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
1769 if (newSize <
size()) {
1772 outputStream.close();
1777 diag.emplace_back(DiagLevel::Critical,
"Unable to truncate the file.", context);
1782 outputStream.flush();