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>
12#include <c++utilities/io/path.h>
58 if (mediaDataAtom && userDataAtom) {
70 if (mediaDataAtom && movieAtom) {
79 CPP_UTILITIES_UNUSED(progress)
88 stream().seekg(
static_cast<iostream::off_type
>(ftypAtom->dataOffset()));
95 CPP_UTILITIES_UNUSED(progress)
96 const string context(
"parsing tags of MP4 container");
102 bool surplusMetaAtoms =
false;
104 metaAtom->parse(diag);
105 m_tags.emplace_back(make_unique<Mp4Tag>());
107 m_tags.back()->parse(*metaAtom, diag);
112 surplusMetaAtoms =
true;
118 if (surplusMetaAtoms) {
119 diag.emplace_back(
DiagLevel::Warning,
"udta atom contains multiple meta atoms. Surplus meta atoms will be ignored.", context);
125 static const string context(
"parsing tracks of MP4 container");
131 if (mvhdAtom->dataSize() > 0) {
132 stream().seekg(
static_cast<iostream::off_type
>(mvhdAtom->dataOffset()));
134 if ((
version == 1 && mvhdAtom->dataSize() >= 32) || (mvhdAtom->dataSize() >= 20)) {
135 stream().seekg(3, ios_base::cur);
141 m_duration = TimeSpan::fromSeconds(
static_cast<TimeSpan::TickType
>(
reader().readUInt32BE()))
148 m_duration = TimeSpan::fromSeconds(
static_cast<TimeSpan::TickType
>(
reader().readUInt64BE()))
165 if (mehdAtom->dataSize() > 0) {
166 stream().seekg(
static_cast<iostream::off_type
>(mehdAtom->dataOffset()));
167 unsigned int durationSize =
reader().readByte() == 1u ? 8u : 4u;
168 if (mehdAtom->dataSize() >= 4 + durationSize) {
169 stream().seekg(3, ios_base::cur);
170 switch (durationSize) {
189 trakAtom->
parse(diag);
191 diag.emplace_back(
DiagLevel::Warning,
"Unable to parse child atom of moov.", context);
194 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
196 m_tracks.back()->parseHeader(diag, progress);
198 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse track ", trackNum,
'.'), context);
225 static const string context(
"making MP4 container");
226 progress.
updateStep(
"Calculating atom sizes and padding ...");
252 std::uint64_t newPadding;
254 std::uint64_t newPaddingEnd;
256 std::uint64_t currentOffset;
258 vector<tuple<istream *, vector<std::uint64_t>, vector<std::uint64_t>>> trackInfos;
260 vector<std::int64_t> origMediaDataOffsets;
262 vector<std::int64_t> newMediaDataOffsets;
264 std::uint64_t movieAtomSize, userDataAtomSize;
268 auto mediaSize = std::uint64_t();
271 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom ;
272 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten =
nullptr;
280 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"ftyp\"-atom not found in the source file.", context);
293 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"moov\"-atom not found in the source file.", context);
301 if (writeChunkByChunk) {
302 diag.emplace_back(
DiagLevel::Critical,
"Writing chunk-by-chunk is not implemented for DASH files.", context);
311 for (firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
312 level0Atom->
parse(diag);
313 switch (level0Atom->
id()) {
321 if (!firstMediaDataAtom) {
322 firstMediaDataAtom = level0Atom;
330 if (firstMediaDataAtom) {
333 newTagPos = currentTagPos;
340 if (firstMovieFragmentAtom) {
343 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
356 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
364 vector<Mp4TagMaker> tagMaker;
365 std::uint64_t tagsSize = 0;
366 tagMaker.reserve(
m_tags.size());
370 tagsSize += tagMaker.back().requiredSize();
376calculateMovieAtomSize:
377 movieAtomSize = userDataAtomSize = 0;
382 level1Atom->
parse(diag);
383 switch (level1Atom->
id()) {
387 level2Atom->
parse(diag);
388 switch (level2Atom->
id()) {
394 userDataAtomSize += level2Atom->
totalSize();
401 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
409 movieAtomSize += level1Atom->
totalSize();
416 if (userDataAtomSize += tagsSize) {
418 movieAtomSize += userDataAtomSize;
430 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
437 if (!rewriteRequired) {
439 std::uint64_t currentSum = 0;
440 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
441 level0Atom->
parse(diag);
442 switch (level0Atom->
id()) {
452 newPaddingEnd += currentSum;
454 lastAtomToBeWritten = level0Atom;
461 currentOffset = fileTypeAtom->
totalSize();
463 if (progressiveDownloadInfoAtom) {
464 currentOffset += progressiveDownloadInfoAtom->
totalSize();
470 currentOffset += movieAtomSize;
477 if (rewriteRequired) {
481 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
488 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
489 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
492 if (rewriteRequired) {
494 if (!firstMovieFragmentAtom && !
fileInfo().forceTagPosition() && !
fileInfo().forceIndexPosition()
499 rewriteRequired =
false;
506 goto calculatePadding;
517 currentOffset += newPadding + mediaSize;
518 if (
auto changedChunkOffsetSize =
false; currentOffset > std::numeric_limits<std::uint32_t>::max()) {
522 argsToString(
"Chunk offset table of track ",
track->
id(),
" will not fit new offsets (up to ", currentOffset,
523 "). It will be converted to 64-bit."),
526 changedChunkOffsetSize =
true;
529 if (changedChunkOffsetSize) {
530 goto calculateMovieAtomSize;
541 NativeFileStream backupStream;
542 BinaryWriter outputWriter(&outputStream);
544 if (rewriteRequired) {
545 if (
fileInfo().saveFilePath().empty()) {
550 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
551 }
catch (
const std::ios_base::failure &failure) {
553 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
559 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
563 }
catch (
const std::ios_base::failure &failure) {
564 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
583 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
584 }
catch (
const std::ios_base::failure &failure) {
585 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
598 if (progressiveDownloadInfoAtom) {
599 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
614 for (std::uint8_t pass = 0; pass != 2; ++pass) {
617 auto tracksWritten =
false;
618 const auto writeTracks = [
this, &diag, &tracksWritten] {
625 tracksWritten =
true;
629 auto userDataWritten =
false;
630 auto writeUserData = [level0Atom, level1Atom, level2Atom, movieAtom, &userDataWritten, userDataAtomSize, &outputStream, &outputWriter,
631 &tagMaker, &diag]()
mutable {
632 if (userDataWritten || !userDataAtomSize) {
640 bool metaAtomWritten =
false;
644 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
645 switch (level2Atom->id()) {
648 for (
auto &maker : tagMaker) {
649 maker.make(outputStream, diag);
651 metaAtomWritten =
true;
655 level2Atom->copyBuffer(outputStream);
656 level2Atom->discardBuffer();
663 if (!metaAtomWritten) {
664 for (
auto &maker : tagMaker) {
665 maker.make(outputStream, diag);
669 userDataWritten =
true;
679 switch (level1Atom->id()) {
688 level1Atom->copyBuffer(outputStream);
689 level1Atom->discardBuffer();
702 if (newPadding < numeric_limits<std::uint32_t>::max()) {
703 outputWriter.writeUInt32BE(
static_cast<std::uint32_t
>(newPadding));
707 outputWriter.writeUInt32BE(1);
709 outputWriter.writeUInt64BE(newPadding);
716 if (rewriteRequired) {
717 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
718 level0Atom->
parse(diag);
719 switch (level0Atom->
id()) {
727 if (writeChunkByChunk) {
732 origMediaDataOffsets.push_back(
static_cast<std::int64_t
>(level0Atom->
startOffset()));
733 newMediaDataOffsets.push_back(outputStream.tellp());
740 level0Atom->
copyEntirely(outputStream, diag, &progress);
745 if (writeChunkByChunk) {
747 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
749 std::uint64_t totalChunkCount = 0;
750 std::uint64_t totalMediaDataSize = 0;
755 trackInfos.emplace_back(
759 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
760 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
763 "Chunks of track " % numberToString<std::uint64_t, string>(
track->
id()) +
" could not be parsed correctly.",
769 totalMediaDataSize += std::accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(),
static_cast<std::uint64_t
>(0u));
778 CopyHelper<0x2000> copyHelper;
779 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
780 bool anyChunksCopied;
785 anyChunksCopied =
false;
786 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
788 auto &trackInfo = trackInfos[trackIndex];
789 istream &sourceStream = *get<0>(trackInfo);
790 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
791 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
794 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
796 sourceStream.seekg(
static_cast<streamoff
>(chunkOffsetTable[chunkIndexWithinTrack]));
797 chunkOffsetTable[chunkIndexWithinTrack] =
static_cast<std::uint64_t
>(outputStream.tellp());
798 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
801 anyChunksCopied =
true;
807 if (!(++chunkIndexWithinTrack % 10)) {
808 progress.
updateStepPercentage(
static_cast<std::uint8_t
>(totalChunksCopied * 100 / totalChunkCount));
811 }
while (anyChunksCopied);
816 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
817 level0Atom->
parse(diag);
818 switch (level0Atom->
id()) {
823 outputStream.seekp(4, ios_base::cur);
827 outputStream.seekp(
static_cast<iostream::off_type
>(level0Atom->
totalSize()), ios_base::cur);
829 if (level0Atom == lastAtomToBeWritten) {
838 progress.
updateStep(
"Reparsing output file ...");
839 if (rewriteRequired) {
843 if (!
fileInfo().saveFilePath().empty()) {
848 outputStream.close();
852 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
856 outputStream.close();
858 auto ec = std::error_code();
863 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
883 if (rewriteRequired) {
887 argsToString(
"Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (",
tracks().size(),
888 ") differs from the number of tracks in the original file (",
trackCount,
")."),
894 if (writeChunkByChunk) {
895 progress.
updateStep(
"Updating chunk offset table for each track ...");
896 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
898 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
903 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
904 ": Number of chunks in the output file differs from the number of chunks in the original file."),
910 progress.
updateStep(
"Updating chunk offset table for each track ...");
911 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag, progress);
916 outputStream.flush();
936void Mp4Container::updateOffsets(
const std::vector<std::int64_t> &oldMdatOffsets,
const std::vector<std::int64_t> &newMdatOffsets,
Diagnostics &diag,
940 const string context(
"updating MP4 container chunk offset table");
949 moofAtom->parse(diag);
953 trafAtom->parse(diag);
954 int tfhdAtomCount = 0;
957 tfhdAtom->parse(diag);
959 if (tfhdAtom->dataSize() < 8) {
963 stream().seekg(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 1);
964 std::uint32_t flags =
reader().readUInt24BE();
968 if (tfhdAtom->dataSize() < 16) {
969 diag.emplace_back(
DiagLevel::Warning,
"tfhd atom (denoting base-data-offset-present) is truncated.", context);
972 stream().seekg(4, ios_base::cur);
973 std::uint64_t off =
reader().readUInt64BE();
974 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
976 if (off <
static_cast<std::uint64_t
>(*iOld)) {
979 off +=
static_cast<std::uint64_t
>(*iNew - *iOld);
980 stream().seekp(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 8);
981 writer().writeUInt64BE(off);
985 switch (tfhdAtomCount) {
987 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
993 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
996 }
catch (
const Failure &) {
997 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse children of top-level atom moof.", context);
1000 }
catch (
const Failure &) {
1008 }
catch (
const Failure &) {
1010 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
1017 }
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.
const std::string & path() const
Returns the path of the current file.
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
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
Mp4Atom * firstElement() const
std::vector< std::unique_ptr< Mp4Tag > > m_tags
std::size_t trackCount() const override
void reset() override
Discards all parsing results.
void copyEntirely(TargetStream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
void copyBuffer(TargetStream &targetStream)
Copies buffered data to targetStream.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
const IdentifierType & id() const
Returns the element ID.
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, IdType 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.
static const CppUtilities::DateTime epoch
Dates within MP4 tracks are expressed as the number of seconds since this date.
void reset() override
Discards all parsing results.
Mp4Container(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Constructs a new container for the specified fileInfo at the specified startOffset.
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/co64 atom.
void setChunkOffsetSize(unsigned int chunkOffsetSize)
Sets the size of a single chunk offset denotation within the stco/co64 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.
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco/co64 atom.
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 handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
@ ProgressiveDownloadInformation
Contains all classes and functions of the TagInfo library.