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/copy.h>
32 Mp4Container::Mp4Container(
MediaFileInfo &fileInfo, std::uint64_t startOffset)
53 if (mediaDataAtom && userDataAtom) {
65 if (mediaDataAtom && movieAtom) {
74 CPP_UTILITIES_UNUSED(progress)
83 stream().seekg(
static_cast<iostream::off_type
>(ftypAtom->dataOffset()));
90 CPP_UTILITIES_UNUSED(progress)
91 const string context(
"parsing tags of MP4 container");
97 bool surplusMetaAtoms =
false;
99 metaAtom->
parse(diag);
100 m_tags.emplace_back(make_unique<Mp4Tag>());
102 m_tags.back()->parse(*metaAtom, diag);
107 surplusMetaAtoms =
true;
113 if (surplusMetaAtoms) {
114 diag.emplace_back(
DiagLevel::Warning,
"udta atom contains multiple meta atoms. Surplus meta atoms will be ignored.", context);
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(
static_cast<double>(
reader().readUInt64BE()));
140 m_modificationTime = DateTime::fromDate(1904, 1, 1) + TimeSpan::fromSeconds(
static_cast<double>(
reader().readUInt64BE()));
158 if (mehdAtom->dataSize() > 0) {
159 stream().seekg(
static_cast<iostream::off_type
>(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) {
182 trakAtom->
parse(diag);
184 diag.emplace_back(
DiagLevel::Warning,
"Unable to parse child atom of moov.", context);
187 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
189 m_tracks.back()->parseHeader(diag, progress);
191 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse track ", trackNum,
'.'), context);
218 static const string context(
"making MP4 container");
219 progress.
updateStep(
"Calculating atom sizes and padding ...");
245 std::uint64_t newPadding;
247 std::uint64_t newPaddingEnd;
249 std::uint64_t currentOffset;
251 vector<tuple<istream *, vector<std::uint64_t>, vector<std::uint64_t>>> trackInfos;
253 vector<std::int64_t> origMediaDataOffsets;
255 vector<std::int64_t> newMediaDataOffsets;
257 std::uint64_t movieAtomSize, userDataAtomSize;
262 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom ;
263 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
284 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"moov\"-atom not in the source file found.", context);
292 if (writeChunkByChunk) {
293 diag.emplace_back(
DiagLevel::Critical,
"Writing chunk-by-chunk is not implemented for DASH files.", context);
302 for (firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
303 level0Atom->
parse(diag);
304 switch (level0Atom->
id()) {
312 firstMediaDataAtom = level0Atom;
319 if (firstMediaDataAtom) {
322 newTagPos = currentTagPos;
329 if (firstMovieFragmentAtom) {
332 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
345 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
353 vector<Mp4TagMaker> tagMaker;
354 std::uint64_t tagsSize = 0;
355 tagMaker.reserve(
m_tags.size());
359 tagsSize += tagMaker.back().requiredSize();
365 movieAtomSize = userDataAtomSize = 0;
370 level1Atom->
parse(diag);
371 switch (level1Atom->
id()) {
375 level2Atom->
parse(diag);
376 switch (level2Atom->
id()) {
382 userDataAtomSize += level2Atom->
totalSize();
389 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
397 movieAtomSize += level1Atom->
totalSize();
404 if (userDataAtomSize += tagsSize) {
406 movieAtomSize += userDataAtomSize;
418 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
425 if (!rewriteRequired) {
427 std::uint64_t currentSum = 0;
428 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
429 level0Atom->
parse(diag);
430 switch (level0Atom->
id()) {
440 newPaddingEnd += currentSum;
442 lastAtomToBeWritten = level0Atom;
449 if (rewriteRequired) {
453 currentOffset = fileTypeAtom->
totalSize();
456 if (progressiveDownloadInfoAtom) {
457 currentOffset += progressiveDownloadInfoAtom->
totalSize();
464 currentOffset += movieAtomSize;
470 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
476 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
477 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
480 if (rewriteRequired) {
487 rewriteRequired =
false;
494 goto calculatePadding;
511 NativeFileStream backupStream;
512 BinaryWriter outputWriter(&outputStream);
514 if (rewriteRequired) {
515 if (
fileInfo().saveFilePath().empty()) {
521 }
catch (
const std::ios_base::failure &failure) {
523 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
529 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
533 }
catch (
const std::ios_base::failure &failure) {
534 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
553 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
554 }
catch (
const std::ios_base::failure &failure) {
555 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
568 if (progressiveDownloadInfoAtom) {
569 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
584 for (std::uint8_t pass = 0; pass != 2; ++pass) {
587 auto tracksWritten =
false;
588 const auto writeTracks = [
this, &diag, &tracksWritten] {
595 tracksWritten =
true;
599 auto userDataWritten =
false;
600 auto writeUserData = [level0Atom, level1Atom, level2Atom, movieAtom, &userDataWritten, userDataAtomSize, &outputStream, &outputWriter,
601 &tagMaker, &diag]()
mutable {
602 if (userDataWritten || !userDataAtomSize) {
610 bool metaAtomWritten =
false;
614 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
615 switch (level2Atom->id()) {
618 for (
auto &maker : tagMaker) {
619 maker.make(outputStream, diag);
621 metaAtomWritten =
true;
625 level2Atom->copyBuffer(outputStream);
626 level2Atom->discardBuffer();
633 if (!metaAtomWritten) {
634 for (
auto &maker : tagMaker) {
635 maker.make(outputStream, diag);
639 userDataWritten =
true;
649 switch (level1Atom->id()) {
658 level1Atom->copyBuffer(outputStream);
659 level1Atom->discardBuffer();
672 if (newPadding < numeric_limits<std::uint32_t>::max()) {
673 outputWriter.writeUInt32BE(
static_cast<std::uint32_t
>(newPadding));
677 outputWriter.writeUInt32BE(1);
679 outputWriter.writeUInt64BE(newPadding);
684 for (; newPadding; --newPadding) {
690 if (rewriteRequired) {
691 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
692 level0Atom->
parse(diag);
693 switch (level0Atom->
id()) {
701 if (writeChunkByChunk) {
706 origMediaDataOffsets.push_back(
static_cast<std::int64_t
>(level0Atom->
startOffset()));
707 newMediaDataOffsets.push_back(outputStream.tellp());
714 level0Atom->
copyEntirely(outputStream, diag, &progress);
719 if (writeChunkByChunk) {
721 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
723 std::uint64_t totalChunkCount = 0;
724 std::uint64_t totalMediaDataSize = 0;
729 trackInfos.emplace_back(
733 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
734 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
737 "Chunks of track " % numberToString<std::uint64_t, string>(
track->
id()) +
" could not be parsed correctly.",
743 totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), 0ul);
753 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
754 bool anyChunksCopied;
759 anyChunksCopied =
false;
760 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
762 auto &trackInfo = trackInfos[trackIndex];
763 istream &sourceStream = *get<0>(trackInfo);
764 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
765 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
768 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
770 sourceStream.seekg(
static_cast<streamoff
>(chunkOffsetTable[chunkIndexWithinTrack]));
771 chunkOffsetTable[chunkIndexWithinTrack] =
static_cast<std::uint64_t
>(outputStream.tellp());
772 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
775 anyChunksCopied =
true;
781 if (!(++chunkIndexWithinTrack % 10)) {
782 progress.
updateStepPercentage(
static_cast<std::uint8_t
>(totalChunksCopied * 100 / totalChunkCount));
785 }
while (anyChunksCopied);
790 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
791 level0Atom->
parse(diag);
792 switch (level0Atom->
id()) {
797 outputStream.seekp(4, ios_base::cur);
801 outputStream.seekp(
static_cast<iostream::off_type
>(level0Atom->
totalSize()), ios_base::cur);
803 if (level0Atom == lastAtomToBeWritten) {
812 progress.
updateStep(
"Reparsing output file ...");
813 if (rewriteRequired) {
817 if (!
fileInfo().saveFilePath().empty()) {
822 outputStream.close();
826 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
830 outputStream.close();
855 if (rewriteRequired) {
859 argsToString(
"Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (",
tracks().size(),
860 ") differs from the number of tracks in the original file (",
trackCount,
")."),
866 if (writeChunkByChunk) {
867 progress.
updateStep(
"Updating chunk offset table for each track ...");
868 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
870 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
875 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
876 ": Number of chunks in the output file differs from the number of chunks in the original file."),
882 progress.
updateStep(
"Updating chunk offset table for each track ...");
883 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag, progress);
888 outputStream.flush();
908 void Mp4Container::updateOffsets(
const std::vector<std::int64_t> &oldMdatOffsets,
const std::vector<std::int64_t> &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 std::uint32_t 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 std::uint64_t off =
reader().readUInt64BE();
946 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
948 if (off <
static_cast<std::uint64_t
>(*iOld)) {
951 off +=
static_cast<std::uint64_t
>(*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 children 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 &) {
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
void nextStepOrStop(const std::string &step, std::uint8_t stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
CppUtilities::DateTime m_modificationTime
std::iostream & stream()
Returns the related stream.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the file if not parsed yet.
std::uint64_t version() const
Returns the version if known; otherwise returns 0.
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
void setStream(std::iostream &stream)
Sets the related stream.
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
CppUtilities::BinaryReader & reader()
Returns the related BinaryReader.
CppUtilities::DateTime m_creationTime
std::uint32_t m_timeScale
CppUtilities::TimeSpan m_duration
std::uint64_t id() const
Returns the track ID if known; otherwise returns 0.
const CppUtilities::DateTime & modificationTime() const
Returns the time of the last modification if known; otherwise returns a DateTime of zero ticks.
const CppUtilities::DateTime & creationTime() const
Returns the creation time if known; otherwise returns a DateTime of zero ticks.
std::istream & inputStream()
Returns the associated input stream.
void parseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses technical information about the track from the header.
bool isHeaderValid() const
Returns an indication whether the track header is valid.
void setOutputStream(std::ostream &stream)
Assigns another output stream.
void setInputStream(std::istream &stream)
Assigns another input stream.
const CppUtilities::TimeSpan & duration() const
Returns the duration if known; otherwise returns a TimeSpan of zero ticks.
const std::string name() const
Returns the track name if known; otherwise returns an empty string.
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
std::uint64_t size() const
Returns size of the current file in bytes.
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
void close()
A possibly opened std::fstream will be closed.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
void updateStep(const std::string &step, std::uint8_t stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
The GenericContainer class helps parsing header, track, tag and chapter information of a file.
const std::vector< std::unique_ptr< Mp4Track > > & tracks() const
Returns the tracks of the file.
Mp4Tag * tag(std::size_t index) override
Mp4Track * track(std::size_t index) override
std::vector< std::unique_ptr< Mp4Track > > m_tracks
std::unique_ptr< Mp4Atom > m_firstElement
MediaFileInfo & fileInfo() const
Returns the related file info.
Mp4Atom * firstElement() const
Returns the first element of the file if available; otherwiese returns nullptr.
std::vector< std::unique_ptr< Mp4Tag > > m_tags
std::size_t trackCount() const override
void reset() override
Discards all parsing results.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
const IdentifierType & id() const
Returns the element ID.
void copyBuffer(std::ostream &targetStream)
Copies buffered data to targetStream.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
ImplementationType * subelementByPath(Diagnostics &diag, IdentifierType item)
Returns the sub element for the specified path.
std::uint64_t totalSize() const
Returns the total size of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
void makeBuffer()
Buffers the element (header and data).
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
The Mp4Atom class helps to parse MP4 files.
static constexpr void addHeaderSize(std::uint64_t &dataSize)
Adds the header size to the specified data size.
std::string idToString() const
Converts the specified atom ID to a printable string.
static void makeHeader(std::uint64_t size, std::uint32_t id, CppUtilities::BinaryWriter &writer)
Writes an MP4 atom header to the specified stream.
ElementPosition determineIndexPosition(Diagnostics &diag) const override
Determines the position of the index.
ElementPosition determineTagPosition(Diagnostics &diag) const override
Determines the position of the tags inside the file.
void reset() override
Discards all parsing results.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
Implementation of TagParser::Tag for the MP4 container.
Mp4TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Implementation of TagParser::AbstractTrack for the MP4 container.
std::uint32_t chunkCount() const
Returns the number of chunks denoted by the stco atom.
std::vector< std::uint64_t > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
void updateChunkOffsets(const std::vector< std::int64_t > &oldMdatOffsets, const std::vector< std::int64_t > &newMdatOffsets)
Updates the chunk offsets of the track.
std::uint64_t requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
std::vector< std::uint64_t > readChunkOffsets(bool parseFragments, Diagnostics &diag)
Reads the chunk offsets from the stco atom and fragments if parseFragments is true.
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
void makeTrack(Diagnostics &diag)
Makes the track entry ("trak"-atom) for the track.
The exception that is thrown when the data to be parsed holds no parsable information (e....
This exception is thrown when the an operation is invoked that has not been implemented yet.
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
@ ProgressiveDownloadInformation
Contains all classes and functions of the TagInfo library.