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/catchiofailure.h> 12 #include <c++utilities/io/copy.h> 56 if (mediaDataAtom && userDataAtom) {
68 if (mediaDataAtom && movieAtom) {
86 stream().seekg(static_cast<iostream::off_type>(ftypAtom->dataOffset()));
93 const string context(
"parsing tags of MP4 container");
99 bool surplusMetaAtoms =
false;
101 metaAtom->parse(diag);
102 m_tags.emplace_back(make_unique<Mp4Tag>());
104 m_tags.back()->parse(*metaAtom, diag);
109 surplusMetaAtoms =
true;
115 if (surplusMetaAtoms) {
116 diag.emplace_back(
DiagLevel::Warning,
"udta atom contains multiple meta atoms. Surplus meta atoms will be ignored.", context);
122 static const string context(
"parsing tracks of MP4 container");
128 if (mvhdAtom->dataSize() > 0) {
129 stream().seekg(static_cast<iostream::off_type>(mvhdAtom->dataOffset()));
131 if ((
version == 1 && mvhdAtom->dataSize() >= 32) || (mvhdAtom->dataSize() >= 20)) {
132 stream().seekg(3, ios_base::cur);
135 m_creationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
reader().readUInt32BE());
141 m_creationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
reader().readUInt64BE());
160 if (mehdAtom->dataSize() > 0) {
161 stream().seekg(static_cast<iostream::off_type>(mehdAtom->dataOffset()));
162 unsigned int durationSize =
reader().readByte() == 1u ? 8u : 4u;
163 if (mehdAtom->dataSize() >= 4 + durationSize) {
164 stream().seekg(3, ios_base::cur);
165 switch (durationSize) {
184 trakAtom->
parse(diag);
186 diag.emplace_back(
DiagLevel::Warning,
"Unable to parse child atom of moov.", context);
189 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
193 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse track ", trackNum,
'.'), context);
220 static const string context(
"making MP4 container");
221 progress.
updateStep(
"Calculating atom sizes and padding ...");
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 ;
265 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
286 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"moov\"-atom not in the source file found.", context);
294 if (writeChunkByChunk) {
295 diag.emplace_back(
DiagLevel::Critical,
"Writing chunk-by-chunk is not implemented for DASH files.", context);
304 for (firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
305 level0Atom->
parse(diag);
306 switch (level0Atom->
id()) {
314 firstMediaDataAtom = level0Atom;
321 if (firstMediaDataAtom) {
324 newTagPos = currentTagPos;
331 if (firstMovieFragmentAtom) {
334 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
347 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
355 vector<Mp4TagMaker> tagMaker;
357 tagMaker.reserve(
m_tags.size());
361 tagsSize += tagMaker.back().requiredSize();
367 movieAtomSize = userDataAtomSize = 0;
372 level1Atom->
parse(diag);
373 switch (level1Atom->
id()) {
377 level2Atom->
parse(diag);
378 switch (level2Atom->
id()) {
384 userDataAtomSize += level2Atom->
totalSize();
391 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
399 movieAtomSize += level1Atom->
totalSize();
406 if (userDataAtomSize += tagsSize) {
408 movieAtomSize += userDataAtomSize;
420 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
427 if (!rewriteRequired) {
429 uint64 currentSum = 0;
430 for (
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
431 level0Atom->
parse(diag);
432 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;
472 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
478 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
479 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
482 if (rewriteRequired) {
484 if (!firstMovieFragmentAtom && !
fileInfo().forceTagPosition() && !
fileInfo().forceIndexPosition()
489 rewriteRequired =
false;
496 goto calculatePadding;
513 NativeFileStream backupStream;
514 BinaryWriter outputWriter(&outputStream);
516 if (rewriteRequired) {
517 if (
fileInfo().saveFilePath().empty()) {
522 outputStream.open(
fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
524 const char *what = catchIoFailure();
525 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
526 throwIoFailure(what);
531 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
532 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
534 outputStream.open(
fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
536 const char *what = catchIoFailure();
537 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
538 throwIoFailure(what);
556 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
558 const char *what = catchIoFailure();
559 diag.emplace_back(
DiagLevel::Critical,
"Opening the file with write permissions failed.", context);
560 throwIoFailure(what);
572 if (progressiveDownloadInfoAtom) {
573 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
588 for (byte pass = 0; pass != 2; ++pass) {
602 switch (level1Atom->
id()) {
616 if (userDataAtomSize) {
625 switch (level2Atom->
id()) {
638 for (
auto &maker : tagMaker) {
639 maker.make(outputStream, diag);
647 if (newPadding < 0xFFFFFFFF) {
648 outputWriter.writeUInt32BE(newPadding);
652 outputWriter.writeUInt32BE(1);
654 outputWriter.writeUInt64BE(newPadding);
659 for (; newPadding; --newPadding) {
665 if (rewriteRequired) {
666 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
667 level0Atom->
parse(diag);
668 switch (level0Atom->
id()) {
676 if (writeChunkByChunk) {
681 origMediaDataOffsets.push_back(level0Atom->
startOffset());
682 newMediaDataOffsets.push_back(outputStream.tellp());
689 level0Atom->
copyEntirely(outputStream, diag, &progress);
694 if (writeChunkByChunk) {
696 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
698 uint64 totalChunkCount = 0;
699 uint64 totalMediaDataSize = 0;
704 trackInfos.emplace_back(
708 const vector<uint64> &chunkOffsetTable = get<1>(trackInfos.back());
709 const vector<uint64> &chunkSizesTable = get<2>(trackInfos.back());
712 "Chunks of track " % numberToString<uint64, string>(
track->
id()) +
" could not be parsed correctly.", context);
717 totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), 0ul);
727 uint64 chunkIndexWithinTrack = 0, totalChunksCopied = 0;
728 bool anyChunksCopied;
733 anyChunksCopied =
false;
734 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
736 auto &trackInfo = trackInfos[trackIndex];
737 istream &sourceStream = *get<0>(trackInfo);
738 vector<uint64> &chunkOffsetTable = get<1>(trackInfo);
739 const vector<uint64> &chunkSizesTable = get<2>(trackInfo);
742 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
744 sourceStream.seekg(chunkOffsetTable[chunkIndexWithinTrack]);
745 chunkOffsetTable[chunkIndexWithinTrack] = outputStream.tellp();
746 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
749 anyChunksCopied =
true;
755 if (!(++chunkIndexWithinTrack % 10)) {
759 }
while (anyChunksCopied);
764 for (
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
765 level0Atom->
parse(diag);
766 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) {
786 progress.
updateStep(
"Reparsing output file ...");
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 (",
tracks().size(),
832 ") differs from the number of tracks in the original file (",
trackCount,
")."),
838 if (writeChunkByChunk) {
839 progress.
updateStep(
"Updating chunk offset table for each track ...");
840 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
842 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
847 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
848 ": Number of chunks in the output file differs from the number of chunks in the orignal file."),
854 progress.
updateStep(
"Updating chunk offset table for each track ...");
855 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag);
860 outputStream.flush();
880 void Mp4Container::updateOffsets(
const std::vector<int64> &oldMdatOffsets,
const std::vector<int64> &newMdatOffsets,
Diagnostics &diag)
883 const string context(
"updating MP4 container chunk offset table");
892 moofAtom->parse(diag);
896 trafAtom->parse(diag);
897 int tfhdAtomCount = 0;
900 tfhdAtom->parse(diag);
902 if (tfhdAtom->dataSize() < 8) {
906 stream().seekg(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 1);
907 uint32 flags =
reader().readUInt24BE();
911 if (tfhdAtom->dataSize() < 16) {
912 diag.emplace_back(
DiagLevel::Warning,
"tfhd atom (denoting base-data-offset-present) is truncated.", context);
915 stream().seekg(4, ios_base::cur);
916 uint64 off =
reader().readUInt64BE();
917 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
919 if (off >= static_cast<uint64>(*iOld)) {
920 off += (*iNew - *iOld);
921 stream().seekp(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 8);
922 writer().writeUInt64BE(off);
927 switch (tfhdAtomCount) {
929 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
935 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
938 }
catch (
const Failure &) {
939 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse childs of top-level atom moof.", context);
942 }
catch (
const Failure &) {
950 }
catch (
const Failure &) {
952 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
959 }
catch (
const Failure &) {
std::vector< uint64 > readChunkOffsets(bool parseFragments, Diagnostics &diag)
Reads the chunk offsets from the stco atom and fragments if parseFragments is true.
std::unique_ptr< Mp4Atom > m_firstElement
const std::string name() const
Returns the track name if known; otherwise returns an empty string.
void updateChunkOffsets(const std::vector< int64 > &oldMdatOffsets, const std::vector< int64 > &newMdatOffsets)
Updates the chunk offsets of the track.
static void addHeaderSize(uint64 &dataSize)
Adds the header size to the specified data size.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
void updateStepPercentage(byte stepPercentage)
Mp4Tag * tag(std::size_t index) override
std::vector< uint64 > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all childs to the specified targetStream.
Mp4Atom * firstElement() const
Returns the first element of the file if available; otherwiese returns nullptr.
void stopIfAborted() const
void makeBuffer()
Buffers the element (header and data).
ImplementationType * nextSibling()
Returns the next sibling of the element.
void internalParseTracks(Diagnostics &diag) override
Internally called to parse the tracks.
ImplementationType * firstChild()
Returns the first child of the element.
void setInputStream(std::istream &stream)
Assigns another input stream.
const std::vector< std::unique_ptr< Mp4Track > > & tracks() const
Returns the tracks of the file.
uint32 chunkCount() const
Returns the number of chunks denoted by the stco atom.
std::iostream & stream()
Returns the related stream.
uint64 startOffset() const
Returns the start offset in the related stream.
void reset() override
Discards all parsing results.
Mp4Track * track(std::size_t index) override
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
ChronoUtilities::DateTime m_creationTime
void parseTracks(Diagnostics &diag)
Parses the tracks of the file if not parsed yet.
uint64 totalSize() const
Returns the total size of the element.
const ChronoUtilities::DateTime & creationTime() const
Returns the creation time if known; otherwise returns a DateTime of zero ticks.
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
void discardBuffer()
Discards buffered data.
ChronoUtilities::TimeSpan m_duration
uint64 version() const
Returns the version if known; otherwise returns 0.
Contains utility classes helping to read and write streams.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
void setStream(std::iostream &stream)
Sets the related stream.
void setOutputStream(std::ostream &stream)
Assigns another output stream.
IoUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
Mp4TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
void internalParseHeader(Diagnostics &diag) override
Internally called to parse the header.
std::vector< std::unique_ptr< Mp4Track > > m_tracks
std::istream & inputStream()
Returns the associated input stream.
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
void close()
A possibly opened std::fstream will be closed.
uint64 id() const
Returns the track ID if known; otherwise returns 0.
void updateStep(const std::string &step, byte stepPercentage=0)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
ChronoUtilities::DateTime m_modificationTime
void copyBuffer(std::ostream &targetStream)
Copies buffered data to targetStream.
void makeTrack(Diagnostics &diag)
Makes the track entry ("trak"-atom) for the track.
IoUtilities::BinaryReader & reader()
Returns the related BinaryReader.
static void makeHeader(uint64 size, uint32 id, IoUtilities::BinaryWriter &writer)
Writes an MP4 atom header to the specified stream.
const ChronoUtilities::TimeSpan & duration() const
Returns the duration if known; otherwise returns a TimeSpan of zero ticks.
void reset() override
Discards all parsing results.
std::size_t trackCount() const override
const ChronoUtilities::DateTime & modificationTime() const
Returns the time of the last modification if known; otherwise returns a DateTime of zero ticks...
void internalParseTags(Diagnostics &diag) override
Internally called to parse the tags.
uint64 startOffset() const
Returns the start offset in the related stream.
uint64 size() const
Returns size of the current file in bytes.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
bool isHeaderValid() const
Returns an indication whether the track header is valid.
uint64 requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
ImplementationType * subelementByPath(Diagnostics &diag, IdentifierType item)
Returns the sub element for the specified path.
void parseHeader(Diagnostics &diag)
Parses technical information about the track from the header.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
MediaFileInfo & fileInfo() const
Returns the related file info.
const IdentifierType & id() const
Returns the element ID.
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
void nextStepOrStop(const std::string &step, byte stepPercentage=0)
std::string idToString() const
Converts the specified atom ID to a printable string.
std::vector< std::unique_ptr< Mp4Tag > > m_tags