4 #include "../exceptions.h" 5 #include "../mediafileinfo.h" 6 #include "../backuphelper.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> 12 #include <c++utilities/io/catchiofailure.h> 35 Mp4Container::Mp4Container(
MediaFileInfo &fileInfo, uint64 startOffset) :
54 if(mediaDataAtom && userDataAtom) {
66 if(mediaDataAtom && movieAtom) {
91 const string context(
"parsing tags of MP4 container");
94 bool surplusMetaAtoms =
false;
97 m_tags.emplace_back(make_unique<Mp4Tag>());
99 m_tags.back()->parse(*metaAtom);
105 surplusMetaAtoms =
true;
111 if(surplusMetaAtoms) {
120 static const string context(
"parsing tracks of MP4 container");
126 if(mvhdAtom->dataSize() > 0) {
127 stream().seekg(static_cast<iostream::off_type>(mvhdAtom->dataOffset()));
129 if((
version == 1 && mvhdAtom->dataSize() >= 32) || (mvhdAtom->dataSize() >= 20)) {
130 stream().seekg(3, ios_base::cur);
133 m_creationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
reader().readUInt32BE());
139 m_creationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
reader().readUInt64BE());
157 if(
Mp4Atom *mehdAtom = moovAtom->
subelementByPath({Mp4AtomIds::MovieExtends, Mp4AtomIds::MovieExtendsHeader})) {
159 if(mehdAtom->dataSize() > 0) {
160 stream().seekg(static_cast<iostream::off_type>(mehdAtom->dataOffset()));
161 unsigned int durationSize =
reader().readByte() == 1u ? 8u : 4u;
162 if(mehdAtom->dataSize() >= 4 + durationSize) {
163 stream().seekg(3, ios_base::cur);
164 switch(durationSize) {
189 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
222 static const string context(
"making MP4 container");
250 uint64 newPaddingEnd;
252 uint64 currentOffset;
254 vector<tuple<istream *, vector<uint64>, vector<uint64> > > trackInfos;
256 vector<int64> origMediaDataOffsets;
258 vector<int64> newMediaDataOffsets;
260 uint64 movieAtomSize, userDataAtomSize;
265 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom;
266 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
295 if(writeChunkByChunk) {
305 for(firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
307 switch(level0Atom->
id()) {
312 firstMediaDataAtom = level0Atom;
319 if(firstMediaDataAtom) {
323 newTagPos = currentTagPos;
330 if(firstMovieFragmentAtom) {
355 vector<Mp4TagMaker> tagMaker;
357 tagMaker.reserve(
m_tags.size());
361 tagsSize += tagMaker.back().requiredSize();
369 movieAtomSize = userDataAtomSize = 0;
375 switch(level1Atom->
id()) {
380 switch(level2Atom->
id()) {
386 userDataAtomSize += level2Atom->
totalSize();
400 movieAtomSize += level1Atom->
totalSize();
407 if(userDataAtomSize += tagsSize) {
409 movieAtomSize += userDataAtomSize;
430 if(!rewriteRequired) {
432 uint64 currentSum = 0;
433 for(
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
435 switch(level0Atom->
id()) {
442 newPaddingEnd += currentSum;
444 lastAtomToBeWritten = level0Atom;
451 if(rewriteRequired) {
455 currentOffset = fileTypeAtom->
totalSize();
458 if(progressiveDownloadInfoAtom) {
459 currentOffset += progressiveDownloadInfoAtom->
totalSize();
466 currentOffset += movieAtomSize;
473 if(!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
479 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
482 if(rewriteRequired) {
488 rewriteRequired =
false;
495 goto calculatePadding;
516 NativeFileStream backupStream;
517 BinaryWriter outputWriter(&outputStream);
519 if(rewriteRequired) {
520 if(
fileInfo().saveFilePath().empty()) {
525 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
527 const char *what = catchIoFailure();
529 throwIoFailure(what);
534 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
535 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
537 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
539 const char *what = catchIoFailure();
541 throwIoFailure(what);
559 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
561 const char *what = catchIoFailure();
563 throwIoFailure(what);
575 if(progressiveDownloadInfoAtom) {
576 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
591 for(byte pass = 0; pass != 2; ++pass) {
605 switch(level1Atom->
id()) {
619 if(userDataAtomSize) {
627 switch(level2Atom->
id()) {
640 for(
auto &maker : tagMaker) {
641 maker.make(outputStream);
649 if(newPadding < 0xFFFFFFFF) {
650 outputWriter.writeUInt32BE(newPadding);
654 outputWriter.writeUInt32BE(1);
656 outputWriter.writeUInt64BE(newPadding);
661 for(; newPadding; --newPadding) {
667 if(rewriteRequired) {
668 for(level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
670 switch(level0Atom->
id()) {
675 if(writeChunkByChunk) {
680 origMediaDataOffsets.push_back(level0Atom->
startOffset());
681 newMediaDataOffsets.push_back(outputStream.tellp());
694 if(writeChunkByChunk) {
696 updateStatus(
"Reading chunk offsets and sizes from the original file ...");
698 uint64 totalChunkCount = 0;
699 uint64 totalMediaDataSize = 0;
709 const vector<uint64> &chunkOffsetTable = get<1>(trackInfos.back());
710 const vector<uint64> &chunkSizesTable = get<2>(trackInfos.back());
717 totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), 0ul);
727 uint64 chunkIndexWithinTrack = 0, totalChunksCopied = 0;
728 bool anyChunksCopied;
735 anyChunksCopied =
false;
736 for(
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
738 auto &trackInfo = trackInfos[trackIndex];
739 istream &sourceStream = *get<0>(trackInfo);
740 vector<uint64> &chunkOffsetTable = get<1>(trackInfo);
741 const vector<uint64> &chunkSizesTable = get<2>(trackInfo);
744 if(chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
746 sourceStream.seekg(chunkOffsetTable[chunkIndexWithinTrack]);
747 chunkOffsetTable[chunkIndexWithinTrack] = outputStream.tellp();
748 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
751 anyChunksCopied =
true;
757 if(!(++chunkIndexWithinTrack % 10)) {
761 }
while(anyChunksCopied);
766 for(
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
768 switch(level0Atom->
id()) {
771 outputStream.seekp(4, ios_base::cur);
775 outputStream.seekp(static_cast<iostream::off_type>(level0Atom->
totalSize()), ios_base::cur);
777 if(level0Atom == lastAtomToBeWritten) {
787 if(rewriteRequired) {
796 outputStream.close();
797 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
800 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
804 outputStream.close();
806 if(truncate(
fileInfo().path().c_str(), static_cast<iostream::off_type>(newSize)) == 0) {
812 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
827 if(rewriteRequired) {
831 argsToString(
"Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file (",
833 ") differs from the number of tracks in the original file (",
840 if(writeChunkByChunk) {
841 updateStatus(
"Updating chunk offset table for each track ...");
842 for(
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
844 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
848 addNotification(
NotificationType::Critical, argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
": Number of chunks in the output file differs from the number of chunks in the orignal file."), context);
853 updateOffsets(origMediaDataOffsets, newMediaDataOffsets);
860 outputStream.flush();
880 void Mp4Container::updateOffsets(
const std::vector<int64> &oldMdatOffsets,
const std::vector<int64> &newMdatOffsets)
883 updateStatus(
"Updating chunk offset table for each track ...");
884 const string context(
"updating MP4 container chunk offset table");
898 int tfhdAtomCount = 0;
903 if(tfhdAtom->dataSize() >= 8) {
904 stream().seekg(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 1);
905 uint32 flags =
reader().readUInt24BE();
907 if(tfhdAtom->dataSize() >= 16) {
908 stream().seekg(4, ios_base::cur);
909 uint64 off =
reader().readUInt64BE();
910 for(
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend();
911 iOld != end; ++iOld, ++iNew) {
912 if(off >= static_cast<uint64>(*iOld)) {
913 off += (*iNew - *iOld);
914 stream().seekp(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 8);
915 writer().writeUInt64BE(off);
927 switch(tfhdAtomCount) {
937 }
catch(
const Failure &) {
941 }
catch(
const Failure &) {
947 throw OperationAbortedException();
952 }
catch(
const Failure &) {
960 }
catch(
const Failure &) {
Contains utility classes helping to read and write streams.