4 #include "../backuphelper.h"
5 #include "../exceptions.h"
6 #include "../mediafileinfo.h"
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/io/binaryreader.h>
10 #include <c++utilities/io/binarywriter.h>
11 #include <c++utilities/io/copy.h>
32 Mp4Container::Mp4Container(
MediaFileInfo &fileInfo, std::uint64_t startOffset)
53 if (mediaDataAtom && userDataAtom) {
65 if (mediaDataAtom && movieAtom) {
83 stream().seekg(
static_cast<iostream::off_type
>(ftypAtom->dataOffset()));
90 const string context(
"parsing tags of MP4 container");
96 bool surplusMetaAtoms =
false;
98 metaAtom->parse(diag);
99 m_tags.emplace_back(make_unique<Mp4Tag>());
101 m_tags.back()->parse(*metaAtom, diag);
106 surplusMetaAtoms =
true;
112 if (surplusMetaAtoms) {
113 diag.emplace_back(
DiagLevel::Warning,
"udta atom contains multiple meta atoms. Surplus meta atoms will be ignored.", context);
119 static const string context(
"parsing tracks of MP4 container");
125 if (mvhdAtom->dataSize() > 0) {
126 stream().seekg(
static_cast<iostream::off_type
>(mvhdAtom->dataOffset()));
128 if ((
version == 1 && mvhdAtom->dataSize() >= 32) || (mvhdAtom->dataSize() >= 20)) {
129 stream().seekg(3, ios_base::cur);
132 m_creationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
reader().readUInt32BE());
138 m_creationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
reader().readUInt64BE());
157 if (mehdAtom->dataSize() > 0) {
158 stream().seekg(
static_cast<iostream::off_type
>(mehdAtom->dataOffset()));
159 unsigned int durationSize =
reader().readByte() == 1u ? 8u : 4u;
160 if (mehdAtom->dataSize() >= 4 + durationSize) {
161 stream().seekg(3, ios_base::cur);
162 switch (durationSize) {
181 trakAtom->
parse(diag);
183 diag.emplace_back(
DiagLevel::Warning,
"Unable to parse child atom of moov.", context);
186 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
190 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse track ", trackNum,
'.'), context);
217 static const string context(
"making MP4 container");
218 progress.
updateStep(
"Calculating atom sizes and padding ...");
244 std::uint64_t newPadding;
246 std::uint64_t newPaddingEnd;
248 std::uint64_t currentOffset;
250 vector<tuple<istream *, vector<std::uint64_t>, vector<std::uint64_t>>> trackInfos;
252 vector<std::int64_t> origMediaDataOffsets;
254 vector<std::int64_t> newMediaDataOffsets;
256 std::uint64_t movieAtomSize, userDataAtomSize;
261 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom ;
262 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
283 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"moov\"-atom not in the source file found.", context);
291 if (writeChunkByChunk) {
292 diag.emplace_back(
DiagLevel::Critical,
"Writing chunk-by-chunk is not implemented for DASH files.", context);
301 for (firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
302 level0Atom->
parse(diag);
303 switch (level0Atom->
id()) {
311 firstMediaDataAtom = level0Atom;
318 if (firstMediaDataAtom) {
321 newTagPos = currentTagPos;
328 if (firstMovieFragmentAtom) {
331 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
344 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
352 vector<Mp4TagMaker> tagMaker;
353 std::uint64_t tagsSize = 0;
354 tagMaker.reserve(
m_tags.size());
358 tagsSize += tagMaker.back().requiredSize();
364 movieAtomSize = userDataAtomSize = 0;
369 level1Atom->
parse(diag);
370 switch (level1Atom->
id()) {
374 level2Atom->
parse(diag);
375 switch (level2Atom->
id()) {
381 userDataAtomSize += level2Atom->
totalSize();
388 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
396 movieAtomSize += level1Atom->
totalSize();
403 if (userDataAtomSize += tagsSize) {
405 movieAtomSize += userDataAtomSize;
417 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
424 if (!rewriteRequired) {
426 std::uint64_t currentSum = 0;
427 for (
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
428 level0Atom->
parse(diag);
429 switch (level0Atom->
id()) {
439 newPaddingEnd += currentSum;
441 lastAtomToBeWritten = level0Atom;
448 if (rewriteRequired) {
452 currentOffset = fileTypeAtom->
totalSize();
455 if (progressiveDownloadInfoAtom) {
456 currentOffset += progressiveDownloadInfoAtom->
totalSize();
463 currentOffset += movieAtomSize;
469 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
475 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
476 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
479 if (rewriteRequired) {
486 rewriteRequired =
false;
493 goto calculatePadding;
510 NativeFileStream backupStream;
511 BinaryWriter outputWriter(&outputStream);
513 if (rewriteRequired) {
514 if (
fileInfo().saveFilePath().empty()) {
520 }
catch (
const std::ios_base::failure &failure) {
522 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
528 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
532 }
catch (
const std::ios_base::failure &failure) {
533 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
552 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
553 }
catch (
const std::ios_base::failure &failure) {
554 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
567 if (progressiveDownloadInfoAtom) {
568 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
583 for (std::uint8_t pass = 0; pass != 2; ++pass) {
586 bool tracksWritten =
false;
587 const auto writeTracks = [&] {
595 tracksWritten =
true;
599 bool userDataWritten =
false;
600 const auto writeUserData = [&] {
601 if (userDataWritten || !userDataAtomSize) {
609 bool metaAtomWritten =
false;
614 switch (level2Atom->
id()) {
617 for (
auto &maker : tagMaker) {
618 maker.make(outputStream, diag);
620 metaAtomWritten =
true;
632 if (!metaAtomWritten) {
633 for (
auto &maker : tagMaker) {
634 maker.make(outputStream, diag);
638 userDataWritten =
true;
648 switch (level1Atom->
id()) {
671 if (newPadding < numeric_limits<std::uint32_t>::max()) {
672 outputWriter.writeUInt32BE(
static_cast<std::uint32_t
>(newPadding));
676 outputWriter.writeUInt32BE(1);
678 outputWriter.writeUInt64BE(newPadding);
683 for (; newPadding; --newPadding) {
689 if (rewriteRequired) {
690 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
691 level0Atom->
parse(diag);
692 switch (level0Atom->
id()) {
700 if (writeChunkByChunk) {
705 origMediaDataOffsets.push_back(
static_cast<std::int64_t
>(level0Atom->
startOffset()));
706 newMediaDataOffsets.push_back(outputStream.tellp());
713 level0Atom->
copyEntirely(outputStream, diag, &progress);
718 if (writeChunkByChunk) {
720 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
722 std::uint64_t totalChunkCount = 0;
723 std::uint64_t totalMediaDataSize = 0;
728 trackInfos.emplace_back(
732 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
733 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
736 "Chunks of track " % numberToString<std::uint64_t, string>(
track->
id()) +
" could not be parsed correctly.",
742 totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), 0ul);
752 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
753 bool anyChunksCopied;
758 anyChunksCopied =
false;
759 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
761 auto &trackInfo = trackInfos[trackIndex];
762 istream &sourceStream = *get<0>(trackInfo);
763 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
764 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
767 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
769 sourceStream.seekg(
static_cast<streamoff
>(chunkOffsetTable[chunkIndexWithinTrack]));
770 chunkOffsetTable[chunkIndexWithinTrack] =
static_cast<std::uint64_t
>(outputStream.tellp());
771 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
774 anyChunksCopied =
true;
780 if (!(++chunkIndexWithinTrack % 10)) {
781 progress.
updateStepPercentage(
static_cast<std::uint8_t
>(totalChunksCopied * 100 / totalChunkCount));
784 }
while (anyChunksCopied);
789 for (
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
790 level0Atom->
parse(diag);
791 switch (level0Atom->
id()) {
796 outputStream.seekp(4, ios_base::cur);
800 outputStream.seekp(
static_cast<iostream::off_type
>(level0Atom->
totalSize()), ios_base::cur);
802 if (level0Atom == lastAtomToBeWritten) {
811 progress.
updateStep(
"Reparsing output file ...");
812 if (rewriteRequired) {
816 if (!
fileInfo().saveFilePath().empty()) {
821 outputStream.close();
825 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
829 outputStream.close();
852 if (rewriteRequired) {
856 argsToString(
"Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (",
tracks().size(),
857 ") differs from the number of tracks in the original file (",
trackCount,
")."),
863 if (writeChunkByChunk) {
864 progress.
updateStep(
"Updating chunk offset table for each track ...");
865 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
867 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
872 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
873 ": Number of chunks in the output file differs from the number of chunks in the orignal file."),
879 progress.
updateStep(
"Updating chunk offset table for each track ...");
880 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag);
885 outputStream.flush();
905 void Mp4Container::updateOffsets(
const std::vector<std::int64_t> &oldMdatOffsets,
const std::vector<std::int64_t> &newMdatOffsets,
Diagnostics &diag)
908 const string context(
"updating MP4 container chunk offset table");
917 moofAtom->parse(diag);
921 trafAtom->parse(diag);
922 int tfhdAtomCount = 0;
925 tfhdAtom->parse(diag);
927 if (tfhdAtom->dataSize() < 8) {
931 stream().seekg(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 1);
932 std::uint32_t flags =
reader().readUInt24BE();
936 if (tfhdAtom->dataSize() < 16) {
937 diag.emplace_back(
DiagLevel::Warning,
"tfhd atom (denoting base-data-offset-present) is truncated.", context);
940 stream().seekg(4, ios_base::cur);
941 std::uint64_t off =
reader().readUInt64BE();
942 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
944 if (off <
static_cast<std::uint64_t
>(*iOld)) {
947 off +=
static_cast<std::uint64_t
>(*iNew - *iOld);
948 stream().seekp(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 8);
949 writer().writeUInt64BE(off);
953 switch (tfhdAtomCount) {
955 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
961 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
964 }
catch (
const Failure &) {
965 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse children of top-level atom moof.", context);
968 }
catch (
const Failure &) {
976 }
catch (
const Failure &) {
978 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
985 }
catch (
const Failure &) {