2015-04-22 19:22:01 +02:00
# include "mp4container.h"
# include "mp4ids.h"
# include "../exceptions.h"
# include "../mediafileinfo.h"
# include "../backuphelper.h"
# include <c++utilities/io/binaryreader.h>
# include <c++utilities/io/binarywriter.h>
# include <c++utilities/io/copy.h>
# include <c++utilities/misc/memory.h>
# include <tuple>
using namespace std ;
using namespace IoUtilities ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
namespace Media {
/*!
* \ class Media : : Mp4Container
* \ brief Implementation of GenericContainer < MediaFileInfo , Mp4Tag , Mp4Track , Mp4Atom > .
*/
/*!
* \ brief Constructs a new container for the specified \ a fileInfo at the specified \ a startOffset .
*/
Mp4Container : : Mp4Container ( MediaFileInfo & fileInfo , uint64 startOffset ) :
GenericContainer < MediaFileInfo , Mp4Tag , Mp4Track , Mp4Atom > ( fileInfo , startOffset ) ,
m_fragmented ( false )
{ }
/*!
* \ brief Destroys the container .
*/
Mp4Container : : ~ Mp4Container ( )
{ }
void Mp4Container : : internalParseHeader ( )
{
//const string context("parsing header of MP4 container"); will be used when generating notifications
m_firstElement = make_unique < Mp4Atom > ( * this , startOffset ( ) ) ;
m_firstElement - > parse ( ) ;
Mp4Atom * ftypAtom = m_firstElement - > siblingById ( Mp4AtomIds : : FileType , true ) ;
if ( ftypAtom ) {
stream ( ) . seekg ( ftypAtom - > dataOffset ( ) ) ;
m_doctype = reader ( ) . readString ( 4 ) ;
m_version = reader ( ) . readUInt32BE ( ) ;
} else {
m_doctype = " mp41 " ;
m_version = 0 ;
}
}
void Mp4Container : : internalParseTags ( )
{
const string context ( " parsing tags of MP4 container " ) ;
if ( Mp4Atom * udtaAtom = firstElement ( ) - > subelementByPath ( { Mp4AtomIds : : Movie , Mp4AtomIds : : UserData } ) ) {
Mp4Atom * metaAtom = udtaAtom - > childById ( Mp4AtomIds : : Meta ) ;
bool surplusMetaAtoms = false ;
while ( metaAtom ) {
metaAtom - > parse ( ) ;
m_tags . emplace_back ( make_unique < Mp4Tag > ( ) ) ;
try {
m_tags . back ( ) - > parse ( * metaAtom ) ;
} catch ( NoDataFoundException & ) {
m_tags . pop_back ( ) ;
}
metaAtom = metaAtom - > siblingById ( Mp4AtomIds : : Meta , false ) ;
if ( metaAtom ) {
surplusMetaAtoms = true ;
}
if ( ! m_tags . empty ( ) ) {
break ;
}
}
if ( surplusMetaAtoms ) {
addNotification ( NotificationType : : Warning , " udta atom contains multiple meta atoms. Surplus meta atoms will be ignored. " , context ) ;
}
}
}
void Mp4Container : : internalParseTracks ( )
{
invalidateStatus ( ) ;
static const string context ( " parsing tracks of MP4 container " ) ;
try {
// get moov atom which holds track information
if ( Mp4Atom * moovAtom = firstElement ( ) - > siblingById ( Mp4AtomIds : : Movie , true ) ) {
// get mvhd atom which holds overall track information
if ( Mp4Atom * mvhdAtom = moovAtom - > childById ( Mp4AtomIds : : MovieHeader ) ) {
if ( mvhdAtom - > dataSize ( ) > 0 ) {
stream ( ) . seekg ( mvhdAtom - > dataOffset ( ) ) ;
byte version = reader ( ) . readByte ( ) ;
if ( ( version = = 1 & & mvhdAtom - > dataSize ( ) > = 32 ) | | ( mvhdAtom - > dataSize ( ) > = 20 ) ) {
stream ( ) . seekg ( 3 , ios_base : : cur ) ; // skip flags
switch ( version ) {
case 0 :
m_creationTime = DateTime : : fromDate ( 1904 , 1 , 1 ) + TimeSpan : : fromSeconds ( reader ( ) . readUInt32BE ( ) ) ;
m_modificationTime = DateTime : : fromDate ( 1904 , 1 , 1 ) + TimeSpan : : fromSeconds ( reader ( ) . readUInt32BE ( ) ) ;
m_timeScale = reader ( ) . readUInt32BE ( ) ;
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( reader ( ) . readUInt32BE ( ) ) / static_cast < double > ( m_timeScale ) ) ;
break ;
case 1 :
m_creationTime = DateTime : : fromDate ( 1904 , 1 , 1 ) + TimeSpan : : fromSeconds ( reader ( ) . readUInt64BE ( ) ) ;
m_modificationTime = DateTime : : fromDate ( 1904 , 1 , 1 ) + TimeSpan : : fromSeconds ( reader ( ) . readUInt64BE ( ) ) ;
m_timeScale = reader ( ) . readUInt32BE ( ) ;
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( reader ( ) . readUInt64BE ( ) ) / static_cast < double > ( m_timeScale ) ) ;
break ;
default :
;
}
} else {
addNotification ( NotificationType : : Critical , " mvhd atom is truncated. " , context ) ;
}
} else {
addNotification ( NotificationType : : Critical , " mvhd atom is empty. " , context ) ;
}
} else {
addNotification ( NotificationType : : Critical , " mvhd atom is does not exist. " , context ) ;
}
// get mvex atom which holds default values for fragmented files
if ( Mp4Atom * mehdAtom = moovAtom - > subelementByPath ( { Mp4AtomIds : : MovieExtends , Mp4AtomIds : : MovieExtendsHeader } ) ) {
m_fragmented = true ;
if ( mehdAtom - > dataSize ( ) > 0 ) {
stream ( ) . seekg ( mehdAtom - > dataOffset ( ) ) ;
unsigned int durationSize = reader ( ) . readByte ( ) = = 1u ? 8u : 4u ; // duration size depends on atom version
if ( mehdAtom - > dataSize ( ) > = 4 + durationSize ) {
stream ( ) . seekg ( 3 , ios_base : : cur ) ; // skip flags
switch ( durationSize ) {
case 4u :
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( reader ( ) . readUInt32BE ( ) ) / static_cast < double > ( m_timeScale ) ) ;
break ;
case 8u :
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( reader ( ) . readUInt64BE ( ) ) / static_cast < double > ( m_timeScale ) ) ;
break ;
default :
;
}
} else {
addNotification ( NotificationType : : Warning , " mehd atom is truncated. " , context ) ;
}
}
}
// get first trak atoms which hold information for each track
Mp4Atom * trakAtom = moovAtom - > childById ( Mp4AtomIds : : Track ) ;
int trackNum = 1 ;
while ( trakAtom ) {
try {
trakAtom - > parse ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning , " Unable to parse child atom of moov. " , context ) ;
}
// parse the trak atom using the Mp4Track class
m_tracks . emplace_back ( make_unique < Mp4Track > ( * trakAtom ) ) ;
try { // try to parse header
m_tracks . back ( ) - > parseHeader ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse track " + ConversionUtilities : : numberToString ( trackNum ) + " . " , context ) ;
}
trakAtom = trakAtom - > siblingById ( Mp4AtomIds : : Track , false ) ; // get next trak atom
+ + trackNum ;
}
// get overall duration, creation time and modification time if not determined yet
if ( m_duration . isNull ( ) | | m_modificationTime . isNull ( ) | | m_creationTime . isNull ( ) ) {
for ( const auto & track : tracks ( ) ) {
if ( track - > duration ( ) > m_duration ) {
m_duration = track - > duration ( ) ;
}
if ( track - > modificationTime ( ) > m_modificationTime ) {
m_modificationTime = track - > modificationTime ( ) ;
}
if ( track - > creationTime ( ) < m_creationTime ) {
m_creationTime = track - > creationTime ( ) ;
}
}
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning , " Unable to parse moov atom. " , context ) ;
}
}
void Mp4Container : : internalMakeFile ( )
{
invalidateStatus ( ) ;
static const string context ( " making MP4 container " ) ;
if ( ! firstElement ( ) ) {
addNotification ( NotificationType : : Critical , " No MP4 atoms could be found. " , context ) ;
throw InvalidDataException ( ) ;
}
// prepare rewriting the file
bool writeChunkByChunk = m_tracksAltered ;
updateStatus ( " Preparing for rewriting MP4 file ... " ) ;
fileInfo ( ) . close ( ) ; // ensure the file is close before renaming it
string backupPath ;
fstream & outputStream = fileInfo ( ) . stream ( ) ;
BinaryWriter outputWriter ( & outputStream ) ;
fstream backupStream ; // create a stream to open the backup/original file
try {
BackupHelper : : createBackupFile ( fileInfo ( ) . path ( ) , backupPath , backupStream ) ;
// set backup stream as associated input stream since we still the original data/atoms to write the new file
setStream ( backupStream ) ;
// recreate original file
outputStream . open ( fileInfo ( ) . path ( ) , ios_base : : out | ios_base : : binary | ios_base : : trunc ) ;
// collect needed atoms
Mp4Atom * ftypAtom , * pdinAtom , * moovAtom ;
try {
ftypAtom = firstElement ( ) - > siblingById ( Mp4AtomIds : : FileType , true ) ; // mandatory
if ( ! ftypAtom ) { // throw error if missing
addNotification ( NotificationType : : Critical , " Mandatory ftyp atom not found. " , context ) ;
}
pdinAtom = firstElement ( ) - > siblingById ( Mp4AtomIds : : ProgressiveDownloadInformation , true ) ; // not mandatory
moovAtom = firstElement ( ) - > siblingById ( Mp4AtomIds : : Movie , true ) ; // mandatory
if ( ! moovAtom ) { // throw error if missing
addNotification ( NotificationType : : Critical , " Mandatory moov atom not found. " , context ) ;
throw InvalidDataException ( ) ;
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse the atom strucutre of the source file. " , context ) ;
throw InvalidDataException ( ) ;
}
if ( m_tags . size ( ) > 1 ) {
addNotification ( NotificationType : : Warning , " There are multiple MP4-tags assigned. Only the first one will be attached to the file. " , context ) ;
}
// write all top-level atoms, header boxes be placed first
updateStatus ( " Writing header ... " ) ;
ftypAtom - > copyEntirely ( outputStream ) ;
if ( pdinAtom ) {
pdinAtom - > copyEntirely ( outputStream ) ;
}
ostream : : pos_type newMoovOffset = outputStream . tellp ( ) ;
2015-07-08 01:15:45 +02:00
Mp4Atom * udtaAtom = nullptr ;
2015-04-22 19:22:01 +02:00
uint64 newUdtaOffset = 0u ;
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
}
moovAtom - > copyWithoutChilds ( outputStream ) ;
2015-07-08 00:20:36 +02:00
for ( Mp4Atom * moovChildAtom = moovAtom - > firstChild ( ) ; moovChildAtom ; moovChildAtom = moovChildAtom - > nextSibling ( ) ) { // write child atoms manually, because the child udta has to be altered/ignored
2015-04-22 19:22:01 +02:00
try {
moovChildAtom - > parse ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse childs of moov atom of original file. " , context ) ;
throw InvalidDataException ( ) ;
}
if ( moovChildAtom - > id ( ) = = Mp4AtomIds : : UserData ) {
// found a udta (user data) atom which childs hold tag infromation
if ( ! udtaAtom ) {
udtaAtom = moovChildAtom ;
// check if the udta atom needs to be written
bool writeUdtaAtom = ! m_tags . empty ( ) ; // it has to be written only when a MP4 tag is assigned
if ( ! writeUdtaAtom ) { // or when there is at least one child except the meta atom in the original file
try {
2015-07-08 01:15:45 +02:00
for ( Mp4Atom * udtaChildAtom = udtaAtom - > firstChild ( ) ; udtaChildAtom ; udtaChildAtom = udtaChildAtom - > nextSibling ( ) ) {
2015-04-22 19:22:01 +02:00
udtaChildAtom - > parse ( ) ;
if ( udtaChildAtom - > id ( ) ! = Mp4AtomIds : : Meta ) {
writeUdtaAtom = true ;
2015-07-08 01:15:45 +02:00
break ;
2015-04-22 19:22:01 +02:00
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning ,
" Unable to parse childs of udta atom of original file. These invalid/unknown atoms will be ignored. " , context ) ;
}
}
if ( writeUdtaAtom ) {
updateStatus ( " Writing tag information ... " ) ;
newUdtaOffset = outputStream . tellp ( ) ; // save offset
udtaAtom - > copyHeader ( outputStream ) ; // and write header
// write meta atom if there's a tag assigned
if ( ! m_tags . empty ( ) ) {
try {
m_tags . front ( ) - > make ( outputStream ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning , " Unable to write meta atom (of assigned mp4 tag). " , context ) ;
}
addNotifications ( * m_tags . front ( ) ) ;
}
// write rest of the child atoms of udta atom
try {
2015-07-08 01:15:45 +02:00
for ( Mp4Atom * udtaChildAtom = udtaAtom - > firstChild ( ) ; udtaChildAtom ; udtaChildAtom = udtaChildAtom - > nextSibling ( ) ) {
2015-04-22 19:22:01 +02:00
udtaChildAtom - > parse ( ) ;
if ( udtaChildAtom - > id ( ) ! = Mp4AtomIds : : Meta ) { // skip meta atoms here of course
udtaChildAtom - > copyEntirely ( outputStream ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning ,
" Unable to parse childs of udta atom of original file. These will be ignored. " , context ) ;
}
// write correct size of udta atom
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream , newUdtaOffset ) ;
}
} else {
addNotification ( NotificationType : : Warning , " The source file has multiple udta atoms. Surplus atoms will be ignored. " , context ) ;
}
} else if ( ! writeChunkByChunk | | moovChildAtom - > id ( ) ! = Mp4AtomIds : : Track ) {
// copy trak atoms only when not writing the data chunk-by-chunk
moovChildAtom - > copyEntirely ( outputStream ) ;
}
}
// the original file has no udta atom but there is tag information to be written
if ( ! udtaAtom & & ! m_tags . empty ( ) ) {
updateStatus ( " Writing tag information ... " ) ;
newUdtaOffset = outputStream . tellp ( ) ;
// write udta atom
outputWriter . writeUInt32BE ( 0 ) ; // the size will be written later
outputWriter . writeUInt32BE ( Mp4AtomIds : : UserData ) ;
// write tags
try {
m_tags . front ( ) - > make ( outputStream ) ;
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream , newUdtaOffset ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning , " Unable to write meta atom (of assigned mp4 tag). " , context ) ;
outputStream . seekp ( - 8 , ios_base : : cur ) ;
}
}
// write trak atoms for each currently assigned track, this is only required when writing data chunk-by-chunk
if ( writeChunkByChunk ) {
updateStatus ( " Writing meta information for the tracks ... " ) ;
for ( auto & track : tracks ( ) ) {
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
}
track - > setOutputStream ( outputStream ) ;
track - > makeTrack ( ) ;
}
}
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream , newMoovOffset ) ;
// prepare for writing the actual data
vector < tuple < istream * , vector < uint64 > , vector < uint64 > > > trackInfos ; // used when writing chunk-by-chunk
uint64 totalChunkCount ; // used when writing chunk-by-chunk
vector < int64 > origMdatOffsets ; // used when simply copying mdat
vector < int64 > newMdatOffsets ; // used when simply copying mdat
// write other atoms
2015-07-08 01:15:45 +02:00
for ( Mp4Atom * otherTopLevelAtom = firstElement ( ) ; otherTopLevelAtom ; otherTopLevelAtom = otherTopLevelAtom - > nextSibling ( ) ) {
2015-04-22 19:22:01 +02:00
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
}
try {
otherTopLevelAtom - > parse ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse all top-level atoms of original file. " , context ) ;
throw InvalidDataException ( ) ;
}
using namespace Mp4AtomIds ;
switch ( otherTopLevelAtom - > id ( ) ) {
case FileType : case ProgressiveDownloadInformation : case Movie : case Free : case Skip :
break ;
case MediaData :
if ( writeChunkByChunk ) {
break ; // write actual data separately when writing chunk by chunk
} else {
// store the mdat offsets when not writing chunk by chunk to be able to update the stco tables
origMdatOffsets . push_back ( otherTopLevelAtom - > startOffset ( ) ) ;
newMdatOffsets . push_back ( outputStream . tellp ( ) ) ;
}
default :
updateStatus ( " Writing " + otherTopLevelAtom - > idToString ( ) + " atom ... " ) ;
otherTopLevelAtom - > forwardStatusUpdateCalls ( this ) ;
otherTopLevelAtom - > copyEntirely ( outputStream ) ;
}
}
// when writing chunk by chunk the actual data needs to be written separately
if ( writeChunkByChunk ) {
// get the chunk offset and the chunk size table from the old file to be able to write single chunks later ...
updateStatus ( " Reading chunk offsets and sizes from the original file ... " ) ;
trackInfos . reserve ( tracks ( ) . size ( ) ) ;
totalChunkCount = 0 ;
for ( auto & track : tracks ( ) ) {
if ( & track - > inputStream ( ) = = & outputStream ) {
track - > setInputStream ( backupStream ) ; // ensure the track reads from the original file
}
trackInfos . emplace_back ( & track - > inputStream ( ) , track - > readChunkOffsets ( ) , track - > readChunkSizes ( ) ) ;
const vector < uint64 > & chunkOffsetTable = get < 1 > ( trackInfos . back ( ) ) ;
const vector < uint64 > & chunkSizesTable = get < 2 > ( trackInfos . back ( ) ) ;
totalChunkCount + = track - > chunkCount ( ) ;
if ( track - > chunkCount ( ) ! = chunkOffsetTable . size ( ) | | track - > chunkCount ( ) ! = chunkSizesTable . size ( ) ) {
addNotification ( NotificationType : : Critical , " Chunks of track " + numberToString < uint64 , string > ( track - > id ( ) ) + " could not be parsed correctly. " , context ) ;
}
}
}
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
}
// reparse what is written so far
updateStatus ( " Reparsing output file ... " ) ;
outputStream . close ( ) ; // the outputStream needs to be reopened to be able to read again
outputStream . open ( fileInfo ( ) . path ( ) , ios_base : : in | ios_base : : out | ios_base : : binary ) ;
setStream ( outputStream ) ;
m_headerParsed = false ;
m_tracks . clear ( ) ;
m_tracksParsed = false ;
m_tagsParsed = false ;
try {
parseTracks ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to reparse the header of the new file. " , context ) ;
throw ;
}
if ( writeChunkByChunk ) {
// checking parsed tracks
size_t trackCount = tracks ( ) . size ( ) ;
if ( trackCount ! = trackInfos . size ( ) ) {
if ( trackCount > trackInfos . size ( ) ) {
trackCount = trackInfos . size ( ) ;
}
addNotification ( NotificationType : : Critical , " The track meta data could not be written correctly. Trying to write the chunk data anyways. " , context ) ;
}
// writing single chunks is needed when tracks have been added or removed
updateStatus ( " Writing chunks to mdat atom ... " ) ;
outputStream . seekp ( 0 , ios_base : : end ) ;
ostream : : pos_type newMdatOffset = outputStream . tellp ( ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // denote 64 bit size
writer ( ) . writeUInt32BE ( Mp4AtomIds : : MediaData ) ;
writer ( ) . writeUInt64BE ( 0 ) ; // write size of mdat atom later
CopyHelper < 0x2000 > copyHelper ;
uint64 chunkIndex = 0 ;
uint64 totalChunksCopied = 0 ;
bool chunksCopied ;
do {
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
}
chunksCopied = false ;
for ( size_t trackIndex = 0 ; trackIndex < trackCount ; + + trackIndex ) {
auto & track = tracks ( ) [ trackIndex ] ;
auto & trackInfo = trackInfos [ trackIndex ] ;
istream & sourceStream = * get < 0 > ( trackInfo ) ;
const vector < uint64 > & chunkOffsetTable = get < 1 > ( trackInfo ) ;
const vector < uint64 > & chunkSizesTable = get < 2 > ( trackInfo ) ;
if ( chunkIndex < chunkOffsetTable . size ( ) & & chunkIndex < chunkSizesTable . size ( ) ) {
sourceStream . seekg ( chunkOffsetTable [ chunkIndex ] ) ;
outputStream . seekp ( 0 , ios_base : : end ) ;
uint64 chunkOffset = outputStream . tellp ( ) ;
copyHelper . copy ( sourceStream , outputStream , chunkSizesTable [ chunkIndex ] ) ;
track - > updateChunkOffset ( chunkIndex , chunkOffset ) ;
chunksCopied = true ;
+ + totalChunksCopied ;
}
}
+ + chunkIndex ;
updatePercentage ( static_cast < double > ( totalChunksCopied ) / totalChunkCount ) ;
} while ( chunksCopied ) ;
outputStream . seekp ( 0 , ios_base : : end ) ;
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream , newMdatOffset , true ) ;
} else {
// correct mdat offsets in the stco atom of each track when we've just copied the mdat atom
updateOffsets ( origMdatOffsets , newMdatOffsets ) ;
}
updatePercentage ( 100.0 ) ;
// dispose no longer used resources and flush output file stream
outputStream . flush ( ) ;
} catch ( OperationAbortedException & ) {
setStream ( outputStream ) ;
m_tracksParsed = false ;
m_headerParsed = false ;
m_firstElement . reset ( ) ;
m_tracks . clear ( ) ;
addNotification ( NotificationType : : Information , " Rewriting the file to apply changed tag information has been aborted. " , context ) ;
BackupHelper : : restoreOriginalFileFromBackupFile ( fileInfo ( ) . path ( ) , backupPath , outputStream , backupStream ) ;
throw ;
} catch ( Failure & ) {
setStream ( outputStream ) ;
m_tracksParsed = false ;
m_headerParsed = false ;
m_firstElement . reset ( ) ;
m_tracks . clear ( ) ;
addNotification ( NotificationType : : Critical , " Rewriting the file to apply changed tag information failed. " , context ) ;
BackupHelper : : restoreOriginalFileFromBackupFile ( fileInfo ( ) . path ( ) , backupPath , outputStream , backupStream ) ;
throw ;
} catch ( ios_base : : failure & ) {
setStream ( outputStream ) ;
m_tracksParsed = false ;
m_headerParsed = false ;
m_firstElement . reset ( ) ;
m_tracks . clear ( ) ;
addNotification ( NotificationType : : Critical , " An IO error occured when rewriting the file to apply changed tag information. " , context ) ;
BackupHelper : : restoreOriginalFileFromBackupFile ( fileInfo ( ) . path ( ) , backupPath , outputStream , backupStream ) ;
throw ;
}
}
/*!
* \ brief Update the chunk offsets for each track of the file .
* \ param oldMdatOffsets Specifies a vector holding the old offsets of the " mdat " - atoms .
* \ param newMdatOffsets Specifies a vector holding the new offsets of the " mdat " - atoms .
*
* Internally uses Mp4Track : : updateOffsets ( ) . Offsets stored in the " tfhd " - atom are also
* updated ( this is not tested yet since I don ' t have files using this atom ) .
*
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
* \ throws Throws Media : : Failure or a derived exception when a making
* error occurs .
*/
void Mp4Container : : updateOffsets ( const std : : vector < int64 > & oldMdatOffsets , const std : : vector < int64 > & newMdatOffsets )
{
// do NOT invalidate the status here since this method is internally called by internalMakeFile(), just update the status
updateStatus ( " Updating chunk offset table for each track ... " ) ;
const string context ( " updating MP4 container chunk offset table " ) ;
if ( ! firstElement ( ) ) {
addNotification ( NotificationType : : Critical , " No MP4 atoms could be found. " , context ) ;
throw InvalidDataException ( ) ;
}
// update "base-data-offset-present" of tfhd atom (NOT tested properly)
try {
2015-06-24 00:45:53 +02:00
for ( Mp4Atom * moofAtom = firstElement ( ) - > siblingById ( Mp4AtomIds : : MovieFragment , false ) ;
moofAtom ; moofAtom = moofAtom - > siblingById ( Mp4AtomIds : : MovieFragment , false ) ) {
2015-04-22 19:22:01 +02:00
moofAtom - > parse ( ) ;
try {
2015-06-24 00:45:53 +02:00
for ( Mp4Atom * trafAtom = moofAtom - > childById ( Mp4AtomIds : : TrackFragment ) ; trafAtom ;
2015-07-08 01:15:45 +02:00
trafAtom = trafAtom - > siblingById ( Mp4AtomIds : : TrackFragment , false ) ) {
2015-04-22 19:22:01 +02:00
trafAtom - > parse ( ) ;
int tfhdAtomCount = 0 ;
2015-06-24 00:45:53 +02:00
for ( Mp4Atom * tfhdAtom = trafAtom - > childById ( Mp4AtomIds : : TrackFragmentHeader ) ; tfhdAtom ;
tfhdAtom = tfhdAtom - > siblingById ( Mp4AtomIds : : TrackFragmentHeader , false ) ) {
2015-04-22 19:22:01 +02:00
tfhdAtom - > parse ( ) ;
+ + tfhdAtomCount ;
if ( tfhdAtom - > dataSize ( ) > = 8 ) {
stream ( ) . seekg ( tfhdAtom - > dataOffset ( ) + 1 ) ;
uint32 flags = reader ( ) . readUInt24BE ( ) ;
if ( flags & 1 ) {
if ( tfhdAtom - > dataSize ( ) > = 16 ) {
stream ( ) . seekg ( 4 , ios_base : : cur ) ; // skip track ID
uint64 off = reader ( ) . readUInt64BE ( ) ;
for ( auto iOld = oldMdatOffsets . cbegin ( ) , iNew = newMdatOffsets . cbegin ( ) , end = oldMdatOffsets . cend ( ) ;
iOld ! = end ; + + iOld , + + iNew ) {
if ( off > = static_cast < uint64 > ( * iOld ) ) {
off + = ( * iNew - * iOld ) ;
stream ( ) . seekp ( tfhdAtom - > dataOffset ( ) + 8 ) ;
writer ( ) . writeUInt64BE ( off ) ;
break ;
}
}
} else {
addNotification ( NotificationType : : Warning , " tfhd atom (denoting base-data-offset-present) is truncated. " , context ) ;
}
}
} else {
addNotification ( NotificationType : : Warning , " tfhd atom is truncated. " , context ) ;
}
}
switch ( tfhdAtomCount ) {
case 0 :
addNotification ( NotificationType : : Warning , " traf atom doesn't contain mandatory tfhd atom. " , context ) ;
break ;
case 1 :
break ;
default :
addNotification ( NotificationType : : Warning , " traf atom stores multiple tfhd atoms but it should only contain exactly one tfhd atom. " , context ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse childs of top-level atom moof. " , context ) ;
}
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse top-level atom moof. " , context ) ;
}
// update each track
for ( auto & track : tracks ( ) ) {
if ( isAborted ( ) ) {
throw OperationAbortedException ( ) ;
}
if ( ! track - > isHeaderValid ( ) ) {
try {
track - > parseHeader ( ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning , " The chunk offsets of track " + track - > name ( ) + " couldn't be updated because the track seems to be invalid. The newly written file seems to be damaged. " , context ) ;
}
}
if ( track - > isHeaderValid ( ) ) {
try {
track - > updateChunkOffsets ( oldMdatOffsets , newMdatOffsets ) ;
} catch ( Failure & ) {
addNotification ( NotificationType : : Warning , " The chunk offsets of track " + track - > name ( ) + " couldn't be updated. The altered file is damaged now, restore the backup! " , context ) ;
}
}
}
}
}