4 #include "../exceptions.h" 5 #include "../mediafileinfo.h" 6 #include "../backuphelper.h" 8 #include <c++utilities/io/binaryreader.h> 9 #include <c++utilities/io/binarywriter.h> 10 #include <c++utilities/io/copy.h> 11 #include <c++utilities/io/catchiofailure.h> 12 #include <c++utilities/misc/memory.h> 34 Mp4Container::Mp4Container(
MediaFileInfo &fileInfo, uint64 startOffset) :
53 if(mediaDataAtom && userDataAtom) {
65 if(mediaDataAtom && movieAtom) {
90 const string context(
"parsing tags of MP4 container");
93 bool surplusMetaAtoms =
false;
96 m_tags.emplace_back(make_unique<Mp4Tag>());
98 m_tags.back()->parse(*metaAtom);
104 surplusMetaAtoms =
true;
110 if(surplusMetaAtoms) {
119 static const string context(
"parsing tracks of MP4 container");
125 if(mvhdAtom->dataSize() > 0) {
126 stream().seekg(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());
156 if(
Mp4Atom *mehdAtom = moovAtom->
subelementByPath({Mp4AtomIds::MovieExtends, Mp4AtomIds::MovieExtendsHeader})) {
158 if(mehdAtom->dataSize() > 0) {
159 stream().seekg(mehdAtom->dataOffset());
160 unsigned int durationSize =
reader().readByte() == 1u ? 8u : 4u;
161 if(mehdAtom->dataSize() >= 4 + durationSize) {
162 stream().seekg(3, ios_base::cur);
163 switch(durationSize) {
188 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
221 static const string context(
"making MP4 container");
249 uint64 newPaddingEnd;
251 uint64 currentOffset;
253 vector<tuple<istream *, vector<uint64>, vector<uint64> > > trackInfos;
255 vector<int64> origMediaDataOffsets;
257 vector<int64> newMediaDataOffsets;
259 uint64 movieAtomSize, userDataAtomSize;
264 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom, *userDataAtom, *metaAtom;
265 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
294 if(writeChunkByChunk) {
304 for(firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
306 switch(level0Atom->
id()) {
311 firstMediaDataAtom = level0Atom;
318 if(firstMediaDataAtom) {
322 newTagPos = currentTagPos;
329 if(firstMovieFragmentAtom) {
356 vector<Mp4TagMaker> tagMaker;
358 tagMaker.reserve(
m_tags.size());
362 tagsSize += tagMaker.back().requiredSize();
370 movieAtomSize = userDataAtomSize = 0;
376 switch(level1Atom->
id()) {
381 switch(level2Atom->
id()) {
387 userDataAtomSize += level2Atom->
totalSize();
398 if(!writeChunkByChunk) {
399 movieAtomSize += level1Atom->
totalSize();
405 movieAtomSize += level1Atom->
totalSize();
412 if(userDataAtomSize += tagsSize) {
414 movieAtomSize += userDataAtomSize;
418 if(writeChunkByChunk) {
438 if(!rewriteRequired) {
440 uint64 currentSum = 0;
441 for(
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
443 switch(level0Atom->
id()) {
450 newPaddingEnd += currentSum;
452 lastAtomToBeWritten = level0Atom;
459 if(rewriteRequired) {
463 currentOffset = fileTypeAtom->
totalSize();
466 if(progressiveDownloadInfoAtom) {
467 currentOffset += progressiveDownloadInfoAtom->
totalSize();
474 currentOffset += movieAtomSize;
481 if(!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
487 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
490 if(rewriteRequired) {
496 rewriteRequired =
false;
503 goto calculatePadding;
524 NativeFileStream backupStream;
525 BinaryWriter outputWriter(&outputStream);
527 if(rewriteRequired) {
528 if(
fileInfo().saveFilePath().empty()) {
533 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
535 const char *what = catchIoFailure();
537 throwIoFailure(what);
542 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
543 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
545 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
547 const char *what = catchIoFailure();
549 throwIoFailure(what);
562 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
564 const char *what = catchIoFailure();
566 throwIoFailure(what);
578 if(progressiveDownloadInfoAtom) {
579 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
584 for(byte pass = 0; pass != 2; ++pass) {
591 if(writeChunkByChunk) {
601 switch(level1Atom->
id()) {
606 if(!writeChunkByChunk) {
620 if(userDataAtomSize) {
628 switch(level2Atom->
id()) {
641 for(
auto &maker : tagMaker) {
642 maker.make(outputStream);
650 if(newPadding < 0xFFFFFFFF) {
651 outputWriter.writeUInt32BE(newPadding);
655 outputWriter.writeUInt32BE(1);
657 outputWriter.writeUInt64BE(newPadding);
662 for(; newPadding; --newPadding) {
668 if(rewriteRequired) {
669 for(level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
671 switch(level0Atom->
id()) {
676 if(writeChunkByChunk) {
681 origMediaDataOffsets.push_back(level0Atom->
startOffset());
682 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;
714 const vector<uint64> &chunkOffsetTable = get<1>(trackInfos.back());
715 const vector<uint64> &chunkSizesTable = get<2>(trackInfos.back());
722 totalMediaDataSize = accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), totalMediaDataSize);
732 uint64 chunkIndexWithinTrack = 0, totalChunksCopied = 0;
733 bool anyChunksCopied;
740 anyChunksCopied =
false;
741 for(
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
743 auto &trackInfo = trackInfos[trackIndex];
744 istream &sourceStream = *get<0>(trackInfo);
745 vector<uint64> &chunkOffsetTable = get<1>(trackInfo);
746 const vector<uint64> &chunkSizesTable = get<2>(trackInfo);
749 if(chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
751 sourceStream.seekg(chunkOffsetTable[chunkIndexWithinTrack]);
752 chunkOffsetTable[chunkIndexWithinTrack] = outputStream.tellp();
753 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
756 anyChunksCopied =
true;
762 if(++chunkIndexWithinTrack % 10) {
766 }
while(anyChunksCopied);
771 for(
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
773 switch(level0Atom->
id()) {
776 outputStream.seekp(4, ios_base::cur);
780 outputStream.seekp(level0Atom->
totalSize(), ios_base::cur);
782 if(level0Atom == lastAtomToBeWritten) {
792 if(rewriteRequired) {
801 outputStream.close();
802 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
805 const auto newSize =
static_cast<uint64
>(outputStream.tellp());
809 outputStream.close();
811 if(truncate(
fileInfo().path().c_str(), newSize) == 0) {
817 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
832 if(rewriteRequired) {
836 error <<
"Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file (" <<
tracks().size()
837 <<
") differs from the number of tracks in the original file (" <<
trackCount <<
").";
843 if(writeChunkByChunk) {
844 updateStatus(
"Updating chunk offset table for each track ...");
845 for(
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
847 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
851 addNotification(
NotificationType::Critical,
"Unable to update chunk offsets of track " + numberToString(trackIndex + 1) +
": Number of chunks in the output file differs from the number of chunks in the orignal file.", context);
856 updateOffsets(origMediaDataOffsets, newMediaDataOffsets);
863 outputStream.flush();
883 void Mp4Container::updateOffsets(
const std::vector<int64> &oldMdatOffsets,
const std::vector<int64> &newMdatOffsets)
886 updateStatus(
"Updating chunk offset table for each track ...");
887 const string context(
"updating MP4 container chunk offset table");
901 int tfhdAtomCount = 0;
906 if(tfhdAtom->dataSize() >= 8) {
907 stream().seekg(tfhdAtom->dataOffset() + 1);
908 uint32 flags =
reader().readUInt24BE();
910 if(tfhdAtom->dataSize() >= 16) {
911 stream().seekg(4, ios_base::cur);
912 uint64 off =
reader().readUInt64BE();
913 for(
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend();
914 iOld != end; ++iOld, ++iNew) {
915 if(off >= static_cast<uint64>(*iOld)) {
916 off += (*iNew - *iOld);
917 stream().seekp(tfhdAtom->dataOffset() + 8);
918 writer().writeUInt64BE(off);
930 switch(tfhdAtomCount) {
Contains utility classes helping to read and write streams.