225 static const string context(
"making MP4 container");
226 progress.
updateStep(
"Calculating atom sizes and padding ...");
252 std::uint64_t newPadding;
254 std::uint64_t newPaddingEnd;
256 std::uint64_t currentOffset;
258 vector<tuple<istream *, vector<std::uint64_t>, vector<std::uint64_t>>> trackInfos;
260 vector<std::int64_t> origMediaDataOffsets;
262 vector<std::int64_t> newMediaDataOffsets;
264 std::uint64_t movieAtomSize, userDataAtomSize;
268 auto mediaSize = std::uint64_t();
271 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom ;
272 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten =
nullptr;
280 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"ftyp\"-atom not found in the source file.", context);
293 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"moov\"-atom not found in the source file.", context);
301 if (writeChunkByChunk) {
302 diag.emplace_back(
DiagLevel::Critical,
"Writing chunk-by-chunk is not implemented for DASH files.", context);
311 for (firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
312 level0Atom->
parse(diag);
313 switch (level0Atom->
id()) {
321 if (!firstMediaDataAtom) {
322 firstMediaDataAtom = level0Atom;
330 if (firstMediaDataAtom) {
333 newTagPos = currentTagPos;
340 if (firstMovieFragmentAtom) {
343 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
356 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
364 vector<Mp4TagMaker> tagMaker;
365 std::uint64_t tagsSize = 0;
366 tagMaker.reserve(
m_tags.size());
369 tagMaker.emplace_back(
tag->prepareMaking(diag));
370 tagsSize += tagMaker.back().requiredSize();
376calculateMovieAtomSize:
377 movieAtomSize = userDataAtomSize = 0;
382 level1Atom->
parse(diag);
383 switch (level1Atom->
id()) {
387 level2Atom->
parse(diag);
388 switch (level2Atom->
id()) {
394 userDataAtomSize += level2Atom->
totalSize();
401 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
409 movieAtomSize += level1Atom->
totalSize();
416 if (userDataAtomSize += tagsSize) {
418 movieAtomSize += userDataAtomSize;
423 movieAtomSize +=
track->requiredSize(diag);
430 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
437 if (!rewriteRequired) {
439 std::uint64_t currentSum = 0;
440 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
441 level0Atom->
parse(diag);
442 switch (level0Atom->
id()) {
452 newPaddingEnd += currentSum;
454 lastAtomToBeWritten = level0Atom;
461 currentOffset = fileTypeAtom->
totalSize();
463 if (progressiveDownloadInfoAtom) {
464 currentOffset += progressiveDownloadInfoAtom->
totalSize();
470 currentOffset += movieAtomSize;
477 if (rewriteRequired) {
481 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
488 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
489 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
492 if (rewriteRequired) {
494 if (!firstMovieFragmentAtom && !
fileInfo().forceTagPosition() && !
fileInfo().forceIndexPosition()
499 rewriteRequired =
false;
506 goto calculatePadding;
517 currentOffset += newPadding + mediaSize;
518 if (
auto changedChunkOffsetSize =
false; currentOffset > std::numeric_limits<std::uint32_t>::max()) {
520 if (
track->chunkOffsetSize() < 8) {
522 argsToString(
"Chunk offset table of track ",
track->id(),
" will not fit new offsets (up to ", currentOffset,
523 "). It will be converted to 64-bit."),
525 track->setChunkOffsetSize(8);
526 changedChunkOffsetSize =
true;
529 if (changedChunkOffsetSize) {
530 goto calculateMovieAtomSize;
541 NativeFileStream backupStream;
542 BinaryWriter outputWriter(&outputStream);
544 if (rewriteRequired) {
545 if (
fileInfo().saveFilePath().empty()) {
550 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
551 }
catch (
const std::ios_base::failure &failure) {
553 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
559 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
563 }
catch (
const std::ios_base::failure &failure) {
564 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
577 track->bufferTrackAtoms(diag);
583 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
584 }
catch (
const std::ios_base::failure &failure) {
585 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
598 if (progressiveDownloadInfoAtom) {
599 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
606 if (&
track->inputStream() == &outputStream) {
607 track->setInputStream(backupStream);
610 track->setOutputStream(outputStream);
614 for (std::uint8_t pass = 0; pass != 2; ++pass) {
617 auto tracksWritten =
false;
618 const auto writeTracks = [
this, &diag, &tracksWritten] {
623 track->makeTrack(diag);
625 tracksWritten =
true;
629 auto userDataWritten =
false;
630 auto writeUserData = [level0Atom, level1Atom, level2Atom, movieAtom, &userDataWritten, userDataAtomSize, &outputStream, &outputWriter,
631 &tagMaker, &diag]()
mutable {
632 if (userDataWritten || !userDataAtomSize) {
640 bool metaAtomWritten =
false;
644 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
645 switch (level2Atom->id()) {
648 for (
auto &maker : tagMaker) {
649 maker.make(outputStream, diag);
651 metaAtomWritten =
true;
655 level2Atom->copyBuffer(outputStream);
656 level2Atom->discardBuffer();
663 if (!metaAtomWritten) {
664 for (
auto &maker : tagMaker) {
665 maker.make(outputStream, diag);
669 userDataWritten =
true;
679 switch (level1Atom->id()) {
688 level1Atom->copyBuffer(outputStream);
689 level1Atom->discardBuffer();
702 if (newPadding < numeric_limits<std::uint32_t>::max()) {
703 outputWriter.writeUInt32BE(
static_cast<std::uint32_t
>(newPadding));
707 outputWriter.writeUInt32BE(1);
709 outputWriter.writeUInt64BE(newPadding);
716 if (rewriteRequired) {
717 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
718 level0Atom->
parse(diag);
719 switch (level0Atom->
id()) {
727 if (writeChunkByChunk) {
732 origMediaDataOffsets.push_back(
static_cast<std::int64_t
>(level0Atom->
startOffset()));
733 newMediaDataOffsets.push_back(outputStream.tellp());
740 level0Atom->
copyEntirely(outputStream, diag, &progress);
745 if (writeChunkByChunk) {
747 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
749 std::uint64_t totalChunkCount = 0;
750 std::uint64_t totalMediaDataSize = 0;
755 trackInfos.emplace_back(
756 &
track->inputStream(),
track->readChunkOffsets(
fileInfo().isForcingFullParse(), diag),
track->readChunkSizes(diag));
759 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
760 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
761 if (
track->chunkCount() != chunkOffsetTable.size() ||
track->chunkCount() != chunkSizesTable.size()) {
763 "Chunks of track " % numberToString<std::uint64_t, string>(
track->id()) +
" could not be parsed correctly.",
768 totalChunkCount +=
track->chunkCount();
769 totalMediaDataSize += std::accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(),
static_cast<std::uint64_t
>(0u));
778 CopyHelper<0x2000> copyHelper;
779 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
780 bool anyChunksCopied;
785 anyChunksCopied =
false;
786 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
788 auto &trackInfo = trackInfos[trackIndex];
789 istream &sourceStream = *get<0>(trackInfo);
790 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
791 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
794 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
796 sourceStream.seekg(
static_cast<streamoff
>(chunkOffsetTable[chunkIndexWithinTrack]));
797 chunkOffsetTable[chunkIndexWithinTrack] =
static_cast<std::uint64_t
>(outputStream.tellp());
798 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
801 anyChunksCopied =
true;
807 if (!(++chunkIndexWithinTrack % 10)) {
808 progress.
updateStepPercentage(
static_cast<std::uint8_t
>(totalChunksCopied * 100 / totalChunkCount));
811 }
while (anyChunksCopied);
816 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
817 level0Atom->
parse(diag);
818 switch (level0Atom->
id()) {
823 outputStream.seekp(4, ios_base::cur);
827 outputStream.seekp(
static_cast<iostream::off_type
>(level0Atom->
totalSize()), ios_base::cur);
829 if (level0Atom == lastAtomToBeWritten) {
838 progress.
updateStep(
"Reparsing output file ...");
839 if (rewriteRequired) {
843 if (!
fileInfo().saveFilePath().empty()) {
848 outputStream.close();
852 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
856 outputStream.close();
858 auto ec = std::error_code();
863 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
883 if (rewriteRequired) {
887 argsToString(
"Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (",
tracks().size(),
888 ") differs from the number of tracks in the original file (",
trackCount,
")."),
894 if (writeChunkByChunk) {
895 progress.
updateStep(
"Updating chunk offset table for each track ...");
896 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
898 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
899 if (
track->chunkCount() == chunkOffsetTable.size()) {
900 track->updateChunkOffsets(chunkOffsetTable);
903 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
904 ": Number of chunks in the output file differs from the number of chunks in the original file."),
910 progress.
updateStep(
"Updating chunk offset table for each track ...");
911 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag, progress);
916 outputStream.flush();