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;
269 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom ;
270 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten =
nullptr;
278 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"ftyp\"-atom not found in the source file.", context);
291 diag.emplace_back(
DiagLevel::Critical,
"Mandatory \"moov\"-atom not found in the source file.", context);
299 if (writeChunkByChunk) {
300 diag.emplace_back(
DiagLevel::Critical,
"Writing chunk-by-chunk is not implemented for DASH files.", context);
309 for (firstMediaDataAtom =
nullptr, level0Atom =
firstElement(); level0Atom; level0Atom = level0Atom->
nextSibling()) {
310 level0Atom->
parse(diag);
311 switch (level0Atom->
id()) {
319 firstMediaDataAtom = level0Atom;
326 if (firstMediaDataAtom) {
329 newTagPos = currentTagPos;
336 if (firstMovieFragmentAtom) {
339 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
352 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
360 vector<Mp4TagMaker> tagMaker;
361 std::uint64_t tagsSize = 0;
362 tagMaker.reserve(
m_tags.size());
366 tagsSize += tagMaker.back().requiredSize();
372 movieAtomSize = userDataAtomSize = 0;
377 level1Atom->
parse(diag);
378 switch (level1Atom->
id()) {
382 level2Atom->
parse(diag);
383 switch (level2Atom->
id()) {
389 userDataAtomSize += level2Atom->
totalSize();
396 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
404 movieAtomSize += level1Atom->
totalSize();
411 if (userDataAtomSize += tagsSize) {
413 movieAtomSize += userDataAtomSize;
425 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
432 if (!rewriteRequired) {
434 std::uint64_t currentSum = 0;
435 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
436 level0Atom->
parse(diag);
437 switch (level0Atom->
id()) {
447 newPaddingEnd += currentSum;
449 lastAtomToBeWritten = level0Atom;
456 if (rewriteRequired) {
460 currentOffset = fileTypeAtom->
totalSize();
463 if (progressiveDownloadInfoAtom) {
464 currentOffset += progressiveDownloadInfoAtom->
totalSize();
471 currentOffset += movieAtomSize;
477 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
483 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
484 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
487 if (rewriteRequired) {
494 rewriteRequired =
false;
501 goto calculatePadding;
518 NativeFileStream backupStream;
519 BinaryWriter outputWriter(&outputStream);
521 if (rewriteRequired) {
522 if (
fileInfo().saveFilePath().empty()) {
527 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
528 }
catch (
const std::ios_base::failure &failure) {
530 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
536 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
540 }
catch (
const std::ios_base::failure &failure) {
541 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
560 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
561 }
catch (
const std::ios_base::failure &failure) {
562 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
575 if (progressiveDownloadInfoAtom) {
576 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
591 for (std::uint8_t pass = 0; pass != 2; ++pass) {
594 auto tracksWritten =
false;
595 const auto writeTracks = [
this, &diag, &tracksWritten] {
602 tracksWritten =
true;
606 auto userDataWritten =
false;
607 auto writeUserData = [level0Atom, level1Atom, level2Atom, movieAtom, &userDataWritten, userDataAtomSize, &outputStream, &outputWriter,
608 &tagMaker, &diag]()
mutable {
609 if (userDataWritten || !userDataAtomSize) {
617 bool metaAtomWritten =
false;
621 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
622 switch (level2Atom->id()) {
625 for (
auto &maker : tagMaker) {
626 maker.make(outputStream, diag);
628 metaAtomWritten =
true;
632 level2Atom->copyBuffer(outputStream);
633 level2Atom->discardBuffer();
640 if (!metaAtomWritten) {
641 for (
auto &maker : tagMaker) {
642 maker.make(outputStream, diag);
646 userDataWritten =
true;
655 for (level1Atom = level0Atom->
firstChild(); level1Atom; level1Atom = level1Atom->nextSibling()) {
656 switch (level1Atom->id()) {
665 level1Atom->copyBuffer(outputStream);
666 level1Atom->discardBuffer();
679 if (newPadding < numeric_limits<std::uint32_t>::max()) {
680 outputWriter.writeUInt32BE(
static_cast<std::uint32_t
>(newPadding));
684 outputWriter.writeUInt32BE(1);
686 outputWriter.writeUInt64BE(newPadding);
691 for (; newPadding; --newPadding) {
697 if (rewriteRequired) {
698 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
699 level0Atom->
parse(diag);
700 switch (level0Atom->
id()) {
708 if (writeChunkByChunk) {
713 origMediaDataOffsets.push_back(
static_cast<std::int64_t
>(level0Atom->
startOffset()));
714 newMediaDataOffsets.push_back(outputStream.tellp());
721 level0Atom->
copyEntirely(outputStream, diag, &progress);
726 if (writeChunkByChunk) {
728 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
730 std::uint64_t totalChunkCount = 0;
731 std::uint64_t totalMediaDataSize = 0;
736 trackInfos.emplace_back(
740 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
741 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
744 "Chunks of track " % numberToString<std::uint64_t, string>(
track->
id()) +
" could not be parsed correctly.",
750 totalMediaDataSize += std::accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(),
static_cast<std::uint64_t
>(0u));
759 CopyHelper<0x2000> copyHelper;
760 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
761 bool anyChunksCopied;
766 anyChunksCopied =
false;
767 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
769 auto &trackInfo = trackInfos[trackIndex];
770 istream &sourceStream = *get<0>(trackInfo);
771 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
772 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
775 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
777 sourceStream.seekg(
static_cast<streamoff
>(chunkOffsetTable[chunkIndexWithinTrack]));
778 chunkOffsetTable[chunkIndexWithinTrack] =
static_cast<std::uint64_t
>(outputStream.tellp());
779 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
782 anyChunksCopied =
true;
788 if (!(++chunkIndexWithinTrack % 10)) {
789 progress.
updateStepPercentage(
static_cast<std::uint8_t
>(totalChunksCopied * 100 / totalChunkCount));
792 }
while (anyChunksCopied);
797 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
798 level0Atom->
parse(diag);
799 switch (level0Atom->
id()) {
804 outputStream.seekp(4, ios_base::cur);
808 outputStream.seekp(
static_cast<iostream::off_type
>(level0Atom->
totalSize()), ios_base::cur);
810 if (level0Atom == lastAtomToBeWritten) {
819 progress.
updateStep(
"Reparsing output file ...");
820 if (rewriteRequired) {
824 if (!
fileInfo().saveFilePath().empty()) {
829 outputStream.close();
833 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
837 outputStream.close();
839 auto ec = std::error_code();
844 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
864 if (rewriteRequired) {
868 argsToString(
"Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (",
tracks().size(),
869 ") differs from the number of tracks in the original file (",
trackCount,
")."),
875 if (writeChunkByChunk) {
876 progress.
updateStep(
"Updating chunk offset table for each track ...");
877 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
879 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
884 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
885 ": Number of chunks in the output file differs from the number of chunks in the original file."),
891 progress.
updateStep(
"Updating chunk offset table for each track ...");
892 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag, progress);
897 outputStream.flush();
917void Mp4Container::updateOffsets(
const std::vector<std::int64_t> &oldMdatOffsets,
const std::vector<std::int64_t> &newMdatOffsets,
Diagnostics &diag,
921 const string context(
"updating MP4 container chunk offset table");
930 moofAtom->parse(diag);
934 trafAtom->parse(diag);
935 int tfhdAtomCount = 0;
938 tfhdAtom->parse(diag);
940 if (tfhdAtom->dataSize() < 8) {
944 stream().seekg(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 1);
945 std::uint32_t flags =
reader().readUInt24BE();
949 if (tfhdAtom->dataSize() < 16) {
950 diag.emplace_back(
DiagLevel::Warning,
"tfhd atom (denoting base-data-offset-present) is truncated.", context);
953 stream().seekg(4, ios_base::cur);
954 std::uint64_t off =
reader().readUInt64BE();
955 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
957 if (off <
static_cast<std::uint64_t
>(*iOld)) {
960 off +=
static_cast<std::uint64_t
>(*iNew - *iOld);
961 stream().seekp(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 8);
962 writer().writeUInt64BE(off);
966 switch (tfhdAtomCount) {
968 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
974 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
977 }
catch (
const Failure &) {
978 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse children of top-level atom moof.", context);
981 }
catch (
const Failure &) {
989 }
catch (
const Failure &) {
991 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
998 }
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.
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
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, 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.
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 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 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.