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) {
591 bool tracksWritten =
false;
592 const auto writeTracks = [&] {
600 tracksWritten =
true;
604 bool userDataWritten =
false;
605 const auto writeUserData = [&] {
606 if (userDataWritten || !userDataAtomSize) {
614 bool metaAtomWritten =
false;
619 switch (level2Atom->
id()) {
622 for (
auto &maker : tagMaker) {
623 maker.make(outputStream, diag);
625 metaAtomWritten =
true;
637 if (!metaAtomWritten) {
638 for (
auto &maker : tagMaker) {
639 maker.make(outputStream, diag);
643 userDataWritten =
true;
653 switch (level1Atom->
id()) {
676 if (newPadding < numeric_limits<uint32>::max()) {
677 outputWriter.writeUInt32BE(static_cast<uint32>(newPadding));
681 outputWriter.writeUInt32BE(1);
683 outputWriter.writeUInt64BE(newPadding);
688 for (; newPadding; --newPadding) {
694 if (rewriteRequired) {
695 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
696 level0Atom->
parse(diag);
697 switch (level0Atom->
id()) {
705 if (writeChunkByChunk) {
710 origMediaDataOffsets.push_back(static_cast<int64>(level0Atom->
startOffset()));
711 newMediaDataOffsets.push_back(outputStream.tellp());
718 level0Atom->
copyEntirely(outputStream, diag, &progress);
723 if (writeChunkByChunk) {
725 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
727 uint64 totalChunkCount = 0;
728 uint64 totalMediaDataSize = 0;
733 trackInfos.emplace_back(
737 const vector<uint64> &chunkOffsetTable = get<1>(trackInfos.back());
738 const vector<uint64> &chunkSizesTable = get<2>(trackInfos.back());
741 "Chunks of track " % numberToString<uint64, string>(
track->
id()) +
" could not be parsed correctly.", context);
746 totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), 0ul);
756 uint64 chunkIndexWithinTrack = 0, totalChunksCopied = 0;
757 bool anyChunksCopied;
762 anyChunksCopied =
false;
763 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
765 auto &trackInfo = trackInfos[trackIndex];
766 istream &sourceStream = *get<0>(trackInfo);
767 vector<uint64> &chunkOffsetTable = get<1>(trackInfo);
768 const vector<uint64> &chunkSizesTable = get<2>(trackInfo);
771 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
773 sourceStream.seekg(static_cast<streamoff>(chunkOffsetTable[chunkIndexWithinTrack]));
774 chunkOffsetTable[chunkIndexWithinTrack] = static_cast<uint64>(outputStream.tellp());
775 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
778 anyChunksCopied =
true;
784 if (!(++chunkIndexWithinTrack % 10)) {
788 }
while (anyChunksCopied);
793 for (
Mp4Atom *level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
794 level0Atom->
parse(diag);
795 switch (level0Atom->
id()) {
800 outputStream.seekp(4, ios_base::cur);
804 outputStream.seekp(static_cast<iostream::off_type>(level0Atom->
totalSize()), ios_base::cur);
806 if (level0Atom == lastAtomToBeWritten) {
815 progress.
updateStep(
"Reparsing output file ...");
816 if (rewriteRequired) {
825 outputStream.close();
826 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
829 const auto newSize = static_cast<uint64>(outputStream.tellp());
833 outputStream.close();
835 if (truncate(
fileInfo().path().c_str(), static_cast<iostream::off_type>(newSize)) == 0) {
841 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
856 if (rewriteRequired) {
860 argsToString(
"Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file (",
tracks().size(),
861 ") differs from the number of tracks in the original file (",
trackCount,
")."),
867 if (writeChunkByChunk) {
868 progress.
updateStep(
"Updating chunk offset table for each track ...");
869 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
871 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
876 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
877 ": Number of chunks in the output file differs from the number of chunks in the orignal file."),
883 progress.
updateStep(
"Updating chunk offset table for each track ...");
884 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag);
889 outputStream.flush();
909 void Mp4Container::updateOffsets(
const std::vector<int64> &oldMdatOffsets,
const std::vector<int64> &newMdatOffsets,
Diagnostics &diag)
912 const string context(
"updating MP4 container chunk offset table");
921 moofAtom->parse(diag);
925 trafAtom->parse(diag);
926 int tfhdAtomCount = 0;
929 tfhdAtom->parse(diag);
931 if (tfhdAtom->dataSize() < 8) {
935 stream().seekg(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 1);
936 uint32 flags =
reader().readUInt24BE();
940 if (tfhdAtom->dataSize() < 16) {
941 diag.emplace_back(
DiagLevel::Warning,
"tfhd atom (denoting base-data-offset-present) is truncated.", context);
944 stream().seekg(4, ios_base::cur);
945 uint64 off =
reader().readUInt64BE();
946 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
948 if (off < static_cast<uint64>(*iOld)) {
951 off += static_cast<uint64>(*iNew - *iOld);
952 stream().seekp(static_cast<iostream::off_type>(tfhdAtom->dataOffset()) + 8);
953 writer().writeUInt64BE(off);
957 switch (tfhdAtomCount) {
959 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
965 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
968 }
catch (
const Failure &) {
969 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse childs of top-level atom moof.", context);
972 }
catch (
const Failure &) {
980 }
catch (
const Failure &) {
982 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
989 }
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.