1#define CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
6#include "../backuphelper.h"
7#include "../exceptions.h"
8#include "../mediafileinfo.h"
10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/io/binaryreader.h>
12#include <c++utilities/io/binarywriter.h>
13#include <c++utilities/io/copy.h>
14#include <c++utilities/io/path.h>
60 if (mediaDataAtom && userDataAtom) {
72 if (mediaDataAtom && movieAtom) {
81 CPP_UTILITIES_UNUSED(progress)
90 stream().seekg(
static_cast<iostream::off_type
>(ftypAtom->dataOffset()));
97 CPP_UTILITIES_UNUSED(progress)
98 const string context(
"parsing tags of MP4 container");
104 bool surplusMetaAtoms =
false;
106 metaAtom->
parse(diag);
107 m_tags.emplace_back(make_unique<Mp4Tag>());
109 m_tags.back()->parse(*metaAtom, diag);
114 surplusMetaAtoms =
true;
120 if (surplusMetaAtoms) {
121 diag.emplace_back(
DiagLevel::Warning,
"udta atom contains multiple meta atoms. Surplus meta atoms will be ignored.", context);
127 static const string context(
"parsing tracks of MP4 container");
133 if (mvhdAtom->dataSize() > 0) {
134 stream().seekg(
static_cast<iostream::off_type
>(mvhdAtom->dataOffset()));
136 if ((
version == 1 && mvhdAtom->dataSize() >= 32) || (mvhdAtom->dataSize() >= 20)) {
137 stream().seekg(3, ios_base::cur);
143 m_duration = TimeSpan::fromSeconds(
static_cast<TimeSpan::TickType
>(
reader().readUInt32BE()))
150 m_duration = TimeSpan::fromSeconds(
static_cast<TimeSpan::TickType
>(
reader().readUInt64BE()))
167 if (mehdAtom->dataSize() > 0) {
168 stream().seekg(
static_cast<iostream::off_type
>(mehdAtom->dataOffset()));
169 unsigned int durationSize =
reader().readByte() == 1u ? 8u : 4u;
170 if (mehdAtom->dataSize() >= 4 + durationSize) {
171 stream().seekg(3, ios_base::cur);
172 switch (durationSize) {
191 trakAtom->
parse(diag);
193 diag.emplace_back(
DiagLevel::Warning,
"Unable to parse child atom of moov.", context);
196 m_tracks.emplace_back(make_unique<Mp4Track>(*trakAtom));
198 m_tracks.back()->parseHeader(diag, progress);
200 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Unable to parse track ", trackNum,
'.'), context);
227 static const string context(
"making MP4 container");
228 progress.
updateStep(
"Calculating atom sizes and padding ...");
254 std::uint64_t newPadding;
256 std::uint64_t newPaddingEnd;
258 std::uint64_t currentOffset;
260 vector<tuple<istream *, vector<std::uint64_t>, vector<std::uint64_t>>> trackInfos;
262 vector<std::int64_t> origMediaDataOffsets;
264 vector<std::int64_t> newMediaDataOffsets;
266 std::uint64_t movieAtomSize, userDataAtomSize;
271 Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom ;
272 Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
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 firstMediaDataAtom = level0Atom;
328 if (firstMediaDataAtom) {
331 newTagPos = currentTagPos;
338 if (firstMovieFragmentAtom) {
341 DiagLevel::Warning,
"Sorry, but putting index/tags at the end is not possible when dealing with DASH files.", context);
354 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the overall atom structure of the source file.", context);
362 vector<Mp4TagMaker> tagMaker;
363 std::uint64_t tagsSize = 0;
364 tagMaker.reserve(
m_tags.size());
368 tagsSize += tagMaker.back().requiredSize();
374 movieAtomSize = userDataAtomSize = 0;
379 level1Atom->
parse(diag);
380 switch (level1Atom->
id()) {
384 level2Atom->
parse(diag);
385 switch (level2Atom->
id()) {
391 userDataAtomSize += level2Atom->
totalSize();
398 DiagLevel::Critical,
"Unable to parse the children of \"udta\"-atom of the source file; ignoring them.", context);
406 movieAtomSize += level1Atom->
totalSize();
413 if (userDataAtomSize += tagsSize) {
415 movieAtomSize += userDataAtomSize;
427 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse the children of \"moov\"-atom of the source file.", context);
434 if (!rewriteRequired) {
436 std::uint64_t currentSum = 0;
437 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
438 level0Atom->
parse(diag);
439 switch (level0Atom->
id()) {
449 newPaddingEnd += currentSum;
451 lastAtomToBeWritten = level0Atom;
458 if (rewriteRequired) {
462 currentOffset = fileTypeAtom->
totalSize();
465 if (progressiveDownloadInfoAtom) {
466 currentOffset += progressiveDownloadInfoAtom->
totalSize();
473 currentOffset += movieAtomSize;
479 if (!(rewriteRequired = firstMediaDataAtom && currentOffset > firstMediaDataAtom->
startOffset())) {
485 newPadding = firstMediaDataAtom->
startOffset() - currentOffset;
486 rewriteRequired = (newPadding > 0 && newPadding < 8) || newPadding <
fileInfo().
minPadding()
489 if (rewriteRequired) {
496 rewriteRequired =
false;
503 goto calculatePadding;
520 NativeFileStream backupStream;
521 BinaryWriter outputWriter(&outputStream);
523 if (rewriteRequired) {
524 if (
fileInfo().saveFilePath().empty()) {
529 outputStream.open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
530 }
catch (
const std::ios_base::failure &failure) {
532 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
538 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
542 }
catch (
const std::ios_base::failure &failure) {
543 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
562 outputStream.open(
fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
563 }
catch (
const std::ios_base::failure &failure) {
564 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening the file with write permissions failed: ", failure.what()), context);
577 if (progressiveDownloadInfoAtom) {
578 progressiveDownloadInfoAtom->
copyBuffer(outputStream);
593 for (std::uint8_t pass = 0; pass != 2; ++pass) {
596 auto tracksWritten =
false;
597 const auto writeTracks = [
this, &diag, &tracksWritten] {
604 tracksWritten =
true;
608 auto userDataWritten =
false;
609 auto writeUserData = [level0Atom, level1Atom, level2Atom, movieAtom, &userDataWritten, userDataAtomSize, &outputStream, &outputWriter,
610 &tagMaker, &diag]()
mutable {
611 if (userDataWritten || !userDataAtomSize) {
619 bool metaAtomWritten =
false;
623 for (level2Atom = level1Atom->firstChild(); level2Atom; level2Atom = level2Atom->nextSibling()) {
624 switch (level2Atom->id()) {
627 for (
auto &maker : tagMaker) {
628 maker.make(outputStream, diag);
630 metaAtomWritten =
true;
634 level2Atom->copyBuffer(outputStream);
635 level2Atom->discardBuffer();
642 if (!metaAtomWritten) {
643 for (
auto &maker : tagMaker) {
644 maker.make(outputStream, diag);
648 userDataWritten =
true;
657 for (level1Atom = level0Atom->
firstChild(); level1Atom; level1Atom = level1Atom->nextSibling()) {
658 switch (level1Atom->id()) {
667 level1Atom->copyBuffer(outputStream);
668 level1Atom->discardBuffer();
681 if (newPadding < numeric_limits<std::uint32_t>::max()) {
682 outputWriter.writeUInt32BE(
static_cast<std::uint32_t
>(newPadding));
686 outputWriter.writeUInt32BE(1);
688 outputWriter.writeUInt64BE(newPadding);
693 for (; newPadding; --newPadding) {
699 if (rewriteRequired) {
700 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
701 level0Atom->
parse(diag);
702 switch (level0Atom->
id()) {
710 if (writeChunkByChunk) {
715 origMediaDataOffsets.push_back(
static_cast<std::int64_t
>(level0Atom->
startOffset()));
716 newMediaDataOffsets.push_back(outputStream.tellp());
723 level0Atom->
copyEntirely(outputStream, diag, &progress);
728 if (writeChunkByChunk) {
730 progress.
updateStep(
"Reading chunk offsets and sizes from the original file ...");
732 std::uint64_t totalChunkCount = 0;
733 std::uint64_t totalMediaDataSize = 0;
738 trackInfos.emplace_back(
742 const vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfos.back());
743 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfos.back());
746 "Chunks of track " % numberToString<std::uint64_t, string>(
track->
id()) +
" could not be parsed correctly.",
752 totalMediaDataSize += std::accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(),
static_cast<std::uint64_t
>(0u));
761 CopyHelper<0x2000> copyHelper;
762 std::uint64_t chunkIndexWithinTrack = 0, totalChunksCopied = 0;
763 bool anyChunksCopied;
768 anyChunksCopied =
false;
769 for (
size_t trackIndex = 0; trackIndex <
trackCount; ++trackIndex) {
771 auto &trackInfo = trackInfos[trackIndex];
772 istream &sourceStream = *get<0>(trackInfo);
773 vector<std::uint64_t> &chunkOffsetTable = get<1>(trackInfo);
774 const vector<std::uint64_t> &chunkSizesTable = get<2>(trackInfo);
777 if (chunkIndexWithinTrack < chunkOffsetTable.size() && chunkIndexWithinTrack < chunkSizesTable.size()) {
779 sourceStream.seekg(
static_cast<streamoff
>(chunkOffsetTable[chunkIndexWithinTrack]));
780 chunkOffsetTable[chunkIndexWithinTrack] =
static_cast<std::uint64_t
>(outputStream.tellp());
781 copyHelper.copy(sourceStream, outputStream, chunkSizesTable[chunkIndexWithinTrack]);
784 anyChunksCopied =
true;
790 if (!(++chunkIndexWithinTrack % 10)) {
791 progress.
updateStepPercentage(
static_cast<std::uint8_t
>(totalChunksCopied * 100 / totalChunkCount));
794 }
while (anyChunksCopied);
799 for (level0Atom = firstMediaDataAtom; level0Atom; level0Atom = level0Atom->
nextSibling()) {
800 level0Atom->
parse(diag);
801 switch (level0Atom->
id()) {
806 outputStream.seekp(4, ios_base::cur);
810 outputStream.seekp(
static_cast<iostream::off_type
>(level0Atom->
totalSize()), ios_base::cur);
812 if (level0Atom == lastAtomToBeWritten) {
821 progress.
updateStep(
"Reparsing output file ...");
822 if (rewriteRequired) {
826 if (!
fileInfo().saveFilePath().empty()) {
831 outputStream.close();
835 const auto newSize =
static_cast<std::uint64_t
>(outputStream.tellp());
839 outputStream.close();
841 auto ec = std::error_code();
846 diag.emplace_back(
DiagLevel::Critical,
"Unable to truncate the file: " + ec.message(), context);
866 if (rewriteRequired) {
870 argsToString(
"Unable to update chunk offsets (\"stco\"/\"co64\"-atom): Number of tracks in the output file (",
tracks().size(),
871 ") differs from the number of tracks in the original file (",
trackCount,
")."),
877 if (writeChunkByChunk) {
878 progress.
updateStep(
"Updating chunk offset table for each track ...");
879 for (
size_t trackIndex = 0; trackIndex !=
trackCount; ++trackIndex) {
881 const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
886 argsToString(
"Unable to update chunk offsets of track ", (trackIndex + 1),
887 ": Number of chunks in the output file differs from the number of chunks in the original file."),
893 progress.
updateStep(
"Updating chunk offset table for each track ...");
894 updateOffsets(origMediaDataOffsets, newMediaDataOffsets, diag, progress);
899 outputStream.flush();
919void Mp4Container::updateOffsets(
const std::vector<std::int64_t> &oldMdatOffsets,
const std::vector<std::int64_t> &newMdatOffsets,
Diagnostics &diag,
923 const string context(
"updating MP4 container chunk offset table");
932 moofAtom->parse(diag);
936 trafAtom->parse(diag);
937 int tfhdAtomCount = 0;
940 tfhdAtom->parse(diag);
942 if (tfhdAtom->dataSize() < 8) {
946 stream().seekg(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 1);
947 std::uint32_t flags =
reader().readUInt24BE();
951 if (tfhdAtom->dataSize() < 16) {
952 diag.emplace_back(
DiagLevel::Warning,
"tfhd atom (denoting base-data-offset-present) is truncated.", context);
955 stream().seekg(4, ios_base::cur);
956 std::uint64_t off =
reader().readUInt64BE();
957 for (
auto iOld = oldMdatOffsets.cbegin(), iNew = newMdatOffsets.cbegin(), end = oldMdatOffsets.cend(); iOld != end;
959 if (off <
static_cast<std::uint64_t
>(*iOld)) {
962 off +=
static_cast<std::uint64_t
>(*iNew - *iOld);
963 stream().seekp(
static_cast<iostream::off_type
>(tfhdAtom->dataOffset()) + 8);
964 writer().writeUInt64BE(off);
968 switch (tfhdAtomCount) {
970 diag.emplace_back(
DiagLevel::Warning,
"traf atom doesn't contain mandatory tfhd atom.", context);
976 DiagLevel::Warning,
"traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom.", context);
979 }
catch (
const Failure &) {
980 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse children of top-level atom moof.", context);
983 }
catch (
const Failure &) {
991 }
catch (
const Failure &) {
993 "The chunk offsets of track " %
track->
name() +
" couldn't be updated because the track seems to be invalid..", context);
1000 }
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
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.
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.