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> 22 using namespace ConversionUtilities;
23 using namespace ChronoUtilities;
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 < numeric_limits<uint32>::max()) {
648 outputWriter.writeUInt32BE(static_cast<uint32>(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(static_cast<int64>(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(static_cast<streamoff>(chunkOffsetTable[chunkIndexWithinTrack]));
745 chunkOffsetTable[chunkIndexWithinTrack] = static_cast<uint64>(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)) {
922 off += static_cast<uint64>(*iNew - *iOld);
923 stream().seekp(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 8);
924 writer().writeUInt64BE(off);
928 switch (tfhdAtomCount) {
930 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
936 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
939 }
catch (
const Failure &) {
940 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse childs of top-level atom moof.", context);
943 }
catch (
const Failure &) {
951 }
catch (
const Failure &) {
953 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
960 }
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.
This exception is thrown when the an operation is invoked that has not been implemented yet.
std::unique_ptr< Mp4Atom > m_firstElement
Implementation of TagParser::Tag for the MP4 container.
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.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
void updateStepPercentage(byte stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
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.
static constexpr void addHeaderSize(uint64 &dataSize)
Adds the header size to the specified data size.
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
Throws an OperationAbortedException if aborted.
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.
The Mp4Atom class helps to parse MP4 files.
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.
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
Implementation of TagParser::AbstractTrack for the MP4 container.
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.
The exception that is thrown when the data to be parsed holds no parsable information (e....
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.
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
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)
Updates the current step and invokes the first callback specified on construction.
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.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
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.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
const IdentifierType & id() const
Returns the element ID.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Contains all classes and functions of the TagInfo library.
void nextStepOrStop(const std::string &step, byte stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
std::string idToString() const
Converts the specified atom ID to a printable string.
std::vector< std::unique_ptr< Mp4Tag > > m_tags
The Diagnostics class is a container for DiagMessage.
The GenericContainer class helps parsing header, track, tag and chapter information of a file.