diff options
Diffstat (limited to 'debian/mp4v2/mp4v2-2.0.0~dfsg0/util/mp4chaps.cpp')
-rw-r--r-- | debian/mp4v2/mp4v2-2.0.0~dfsg0/util/mp4chaps.cpp | 1157 |
1 files changed, 0 insertions, 1157 deletions
diff --git a/debian/mp4v2/mp4v2-2.0.0~dfsg0/util/mp4chaps.cpp b/debian/mp4v2/mp4v2-2.0.0~dfsg0/util/mp4chaps.cpp deleted file mode 100644 index 56aa484e..00000000 --- a/debian/mp4v2/mp4v2-2.0.0~dfsg0/util/mp4chaps.cpp +++ /dev/null @@ -1,1157 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License at -// http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -// License for the specific language governing rights and limitations -// under the License. -// -// The Original Code is MP4v2. -// -// The Initial Developer of the Original Code is Ullrich Pollaehne. -// Portions created by Kona Blend are Copyright (C) 2008. -// Portions created by David Byron are Copyright (C) 2010. -// All Rights Reserved. -// -// Contributors: -// Kona Blend, kona8lend@@gmail.com -// Ullrich Pollaehne, u.pollaehne@@gmail.com -// David Byron, [email protected] -// -/////////////////////////////////////////////////////////////////////////////// -#include "util/impl.h" - -namespace mp4v2 { namespace util { - -/////////////////////////////////////////////////////////////////////////////// -/// -/// Chapter utility program class. -/// -/// This class provides an implementation for a QuickTime/Nero chapter utility which -/// allows to add, delete, convert export or import QuickTime and Nero chapters -/// in MP4 container files. -/// -/// -/// @see Utility -/// -/////////////////////////////////////////////////////////////////////////////// -class ChapterUtility : public Utility -{ -private: - static const double CHAPTERTIMESCALE; //!< the timescale used for chapter tracks (1000) - - enum FileLongCode { - LC_CHPT_ANY = _LC_MAX, - LC_CHPT_QT, - LC_CHPT_NERO, - LC_CHPT_COMMON, - LC_CHP_LIST, - LC_CHP_CONVERT, - LC_CHP_EVERY, - LC_CHP_EXPORT, - LC_CHP_IMPORT, - LC_CHP_REMOVE - }; - - enum ChapterFormat { - CHPT_FMT_NATIVE, - CHPT_FMT_COMMON - }; - - enum FormatState { - FMT_STATE_INITIAL, - FMT_STATE_TIME_LINE, - FMT_STATE_TITLE_LINE, - FMT_STATE_FINISH - }; - -public: - ChapterUtility( int, char** ); - -protected: - // delegates implementation - bool utility_option( int, bool& ); - bool utility_job( JobContext& ); - -private: - bool actionList ( JobContext& ); - bool actionConvert ( JobContext& ); - bool actionEvery ( JobContext& ); - bool actionExport ( JobContext& ); - bool actionImport ( JobContext& ); - bool actionRemove ( JobContext& ); - -private: - Group _actionGroup; - Group _parmGroup; - - bool (ChapterUtility::*_action)( JobContext& ); - void fixQtScale(MP4FileHandle ); - MP4TrackId getReferencingTrack( MP4FileHandle, bool& ); - string getChapterTypeName( MP4ChapterType ) const; - bool parseChapterFile( const string, vector<MP4Chapter_t>&, Timecode::Format& ); - bool readChapterFile( const string, char**, File::Size& ); - MP4Duration convertFrameToMillis( MP4Duration, uint32_t ); - - MP4ChapterType _ChapterType; - ChapterFormat _ChapterFormat; - uint32_t _ChaptersEvery; - string _ChapterFile; -}; - -/////////////////////////////////////////////////////////////////////////////// - -const double ChapterUtility::CHAPTERTIMESCALE = 1000.0; - -/////////////////////////////////////////////////////////////////////////////// - -ChapterUtility::ChapterUtility( int argc, char** argv ) - : Utility ( "mp4chaps", argc, argv ) - , _actionGroup ( "ACTIONS" ) - , _parmGroup ( "ACTION PARAMETERS" ) - , _action ( NULL ) - , _ChapterType ( MP4ChapterTypeAny ) - , _ChapterFormat ( CHPT_FMT_NATIVE ) - , _ChaptersEvery ( 0 ) -{ - // add standard options which make sense for this utility - _group.add( STD_OPTIMIZE ); - _group.add( STD_DRYRUN ); - _group.add( STD_KEEPGOING ); - _group.add( STD_OVERWRITE ); - _group.add( STD_FORCE ); - _group.add( STD_QUIET ); - _group.add( STD_DEBUG ); - _group.add( STD_VERBOSE ); - _group.add( STD_HELP ); - _group.add( STD_VERSION ); - _group.add( STD_VERSIONX ); - - _parmGroup.add( 'A', false, "chapter-any", false, LC_CHPT_ANY, "act on any chapter type (default)" ); - _parmGroup.add( 'Q', false, "chapter-qt", false, LC_CHPT_QT, "act on QuickTime chapters" ); - _parmGroup.add( 'N', false, "chapter-nero", false, LC_CHPT_NERO, "act on Nero chapters" ); - _parmGroup.add( 'C', false, "format-common", false, LC_CHPT_COMMON, "export chapters in common format" ); - _groups.push_back( &_parmGroup ); - - _actionGroup.add( 'l', false, "list", false, LC_CHP_LIST, "list available chapters" ); - _actionGroup.add( 'c', false, "convert", false, LC_CHP_CONVERT, "convert available chapters" ); - _actionGroup.add( 'e', true, "every", true, LC_CHP_EVERY, "create chapters every NUM seconds", "NUM" ); - _actionGroup.add( 'x', false, "export", false, LC_CHP_EXPORT, "export chapters to mp4file.chapters.txt", "TXT" ); - _actionGroup.add( 'i', false, "import", false, LC_CHP_IMPORT, "import chapters from mp4file.chapters.txt", "TXT" ); - _actionGroup.add( 'r', false, "remove", false, LC_CHP_REMOVE, "remove all chapters" ); - _groups.push_back( &_actionGroup ); - - _usage = "[OPTION]... ACTION [ACTION PARAMETERS] mp4file..."; - _description = - // 79-cols, inclusive, max desired width - // |----------------------------------------------------------------------------| - "\nFor each mp4 file specified, perform the specified ACTION. An action must be" - "\nspecified. Some options are not applicable to some actions."; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Action for listing chapters from <b>job.file</b> - * - * - * @param job the job to process - * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise - */ -bool -ChapterUtility::actionList( JobContext& job ) -{ - job.fileHandle = MP4Read( job.file.c_str() ); - if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) - { - return herrf( "unable to open for read: %s\n", job.file.c_str() ); - } - - MP4Chapter_t * chapters = 0; - uint32_t chapterCount = 0; - - // get the list of chapters - MP4ChapterType chtp = MP4GetChapters(job.fileHandle, &chapters, &chapterCount, _ChapterType); - if (0 == chapterCount) - { - verbose1f( "File \"%s\" does not contain chapters of type %s\n", job.file.c_str(), - getChapterTypeName( _ChapterType ).c_str() ); - return SUCCESS; - } - - // start output (more or less like mp4box does) - ostringstream report; - report << getChapterTypeName( chtp ) << ' ' << "Chapters of " << '"' << job.file << '"' << endl; - - Timecode duration(0, CHAPTERTIMESCALE); - duration.setFormat( Timecode::DECIMAL ); - for (uint32_t i = 0; i < chapterCount; ++i) - { - // print the infos - report << '\t' << "Chapter #" << setw( 3 ) << setfill( '0' ) << i+1 - << " - " << duration.svalue << " - " << '"' << chapters[i].title << '"' << endl; - - // add the duration of this chapter to the sum (is the start time of the next chapter) - duration += Timecode(chapters[i].duration, CHAPTERTIMESCALE); - } - - verbose1f( "%s", report.str().c_str() ); - - // free up the memory - MP4Free(chapters); - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Action for converting chapters in <b>job.file</b> - * - * - * @param job the job to process - * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise - */ -bool -ChapterUtility::actionConvert( JobContext& job ) -{ - MP4ChapterType sourceType; - - switch( _ChapterType ) - { - case MP4ChapterTypeNero: - sourceType = MP4ChapterTypeQt; - break; - case MP4ChapterTypeQt: - sourceType = MP4ChapterTypeNero; - break; - default: - return herrf( "invalid chapter type \"%s\" define the chapter type to convert to\n", - getChapterTypeName( _ChapterType ).c_str() ); - } - - ostringstream oss; - oss << "converting chapters in file " << '"' << job.file << '"' - << " from " << getChapterTypeName( sourceType ) << " to " << getChapterTypeName( _ChapterType ) << endl; - - verbose1f( "%s", oss.str().c_str() ); - if( dryrunAbort() ) - { - return SUCCESS; - } - - job.fileHandle = MP4Modify( job.file.c_str() ); - if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) - { - return herrf( "unable to open for write: %s\n", job.file.c_str() ); - } - - MP4ChapterType chtp = MP4ConvertChapters( job.fileHandle, _ChapterType ); - if( MP4ChapterTypeNone == chtp ) - { - return herrf( "File %s does not contain chapters of type %s\n", job.file.c_str(), - getChapterTypeName( sourceType ).c_str() ); - } - - fixQtScale( job.fileHandle ); - job.optimizeApplicable = true; - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Action for setting chapters every n second in <b>job.file</b> - * - * - * @param job the job to process - * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise - */ -bool -ChapterUtility::actionEvery( JobContext& job ) -{ - ostringstream oss; - oss << "Setting " << getChapterTypeName( _ChapterType ) << " chapters every " - << _ChaptersEvery << " seconds in file " << '"' << job.file << '"' << endl; - - verbose1f( "%s", oss.str().c_str() ); - if( dryrunAbort() ) - { - return SUCCESS; - } - - job.fileHandle = MP4Modify( job.file.c_str() ); - if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) - { - return herrf( "unable to open for write: %s\n", job.file.c_str() ); - } - - bool isVideoTrack = false; - MP4TrackId refTrackId = getReferencingTrack( job.fileHandle, isVideoTrack ); - if( !MP4_IS_VALID_TRACK_ID(refTrackId) ) - { - return herrf( "unable to find a video or audio track in file %s\n", job.file.c_str() ); - } - - Timecode refTrackDuration( MP4GetTrackDuration( job.fileHandle, refTrackId ), MP4GetTrackTimeScale( job.fileHandle, refTrackId ) ); - refTrackDuration.setScale( CHAPTERTIMESCALE ); - - Timecode chapterDuration( _ChaptersEvery * 1000, CHAPTERTIMESCALE ); - chapterDuration.setFormat( Timecode::DECIMAL ); - vector<MP4Chapter_t> chapters; - - do - { - MP4Chapter_t chap; - chap.duration = refTrackDuration.duration > chapterDuration.duration ? chapterDuration.duration : refTrackDuration.duration; - sprintf(chap.title, "Chapter %lu", (unsigned long)chapters.size()+1); - - chapters.push_back( chap ); - refTrackDuration -= chapterDuration; - } - while( refTrackDuration.duration > 0 ); - - if( 0 < chapters.size() ) - { - MP4SetChapters(job.fileHandle, &chapters[0], (uint32_t)chapters.size(), _ChapterType); - } - - fixQtScale( job.fileHandle ); - job.optimizeApplicable = true; - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Action for exporting chapters from the <b>job.file</b> - * - * - * @param job the job to process - * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise - */ -bool -ChapterUtility::actionExport( JobContext& job ) -{ - job.fileHandle = MP4Read( job.file.c_str() ); - if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) - { - return herrf( "unable to open for read: %s\n", job.file.c_str() ); - } - - // get the list of chapters - MP4Chapter_t* chapters = 0; - uint32_t chapterCount = 0; - MP4ChapterType chtp = MP4GetChapters( job.fileHandle, &chapters, &chapterCount, _ChapterType ); - if (0 == chapterCount) - { - return herrf( "File \"%s\" does not contain chapters of type %s\n", job.file.c_str(), - getChapterTypeName( chtp ).c_str() ); - } - - // build the filename - string outName = job.file; - if( _ChapterFile.empty() ) - { - FileSystem::pathnameStripExtension( outName ); - outName.append( ".chapters.txt" ); - } - else - { - outName = _ChapterFile; - } - - ostringstream oss; - oss << "Exporting " << chapterCount << " " << getChapterTypeName( chtp ); - oss << " chapters from file " << '"' << job.file << '"' << " into chapter file " << '"' << outName << '"' << endl; - - verbose1f( "%s", oss.str().c_str() ); - if( dryrunAbort() ) - { - // free up the memory - MP4Free(chapters); - - return SUCCESS; - } - - // open the file - File out( outName, File::MODE_CREATE ); - if( openFileForWriting( out ) ) - { - // free up the memory - MP4Free(chapters); - - return FAILURE; - } - - // write the chapters -#if defined( _WIN32 ) - static const char* LINEND = "\r\n"; -#else - static const char* LINEND = "\n"; -#endif - File::Size nout; - bool failure = SUCCESS; - int width = 2; - if( CHPT_FMT_COMMON == _ChapterFormat && (chapterCount / 100) >= 1 ) - { - width = 3; - } - Timecode duration( 0, CHAPTERTIMESCALE ); - duration.setFormat( Timecode::DECIMAL ); - for( uint32_t i = 0; i < chapterCount; ++i ) - { - // print the infos - ostringstream oss; - switch( _ChapterFormat ) - { - case CHPT_FMT_COMMON: - oss << "CHAPTER" << setw( width ) << setfill( '0' ) << i+1 << '=' << duration.svalue << LINEND - << "CHAPTER" << setw( width ) << setfill( '0' ) << i+1 << "NAME=" << chapters[i].title << LINEND; - break; - case CHPT_FMT_NATIVE: - default: - oss << duration.svalue << ' ' << chapters[i].title << LINEND; - } - - string str = oss.str(); - if( out.write( str.c_str(), str.size(), nout ) ) - { - failure = herrf( "write to %s failed: %s\n", outName.c_str(), sys::getLastErrorStr() ); - break; - } - - // add the duration of this chapter to the sum (the start time of the next chapter) - duration += Timecode(chapters[i].duration, CHAPTERTIMESCALE); - } - out.close(); - if( failure ) - { - verbose1f( "removing file %s\n", outName.c_str() ); - ::remove( outName.c_str() ); - } - - // free up the memory - MP4Free(chapters); - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Action for importing chapters into the <b>job.file</b> - * - * - * @param job the job to process - * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise - */ -bool -ChapterUtility::actionImport( JobContext& job ) -{ - vector<MP4Chapter_t> chapters; - Timecode::Format format; - - // create the chapter file name - string inName = job.file; - if( _ChapterFile.empty() ) - { - FileSystem::pathnameStripExtension( inName ); - inName.append( ".chapters.txt" ); - } - else - { - inName = _ChapterFile; - } - - if( parseChapterFile( inName, chapters, format ) ) - { - return FAILURE; - } - - ostringstream oss; - oss << "Importing " << chapters.size() << " " << getChapterTypeName( _ChapterType ); - oss << " chapters from file " << inName << " into file " << '"' << job.file << '"' << endl; - - verbose1f( "%s", oss.str().c_str() ); - if( dryrunAbort() ) - { - return SUCCESS; - } - - if( 0 == chapters.size() ) - { - return herrf( "No chapters found in file %s\n", inName.c_str() ); - } - - job.fileHandle = MP4Modify( job.file.c_str() ); - if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) - { - return herrf( "unable to open for write: %s\n", job.file.c_str() ); - } - - bool isVideoTrack = false; - MP4TrackId refTrackId = getReferencingTrack( job.fileHandle, isVideoTrack ); - if( !MP4_IS_VALID_TRACK_ID(refTrackId) ) - { - return herrf( "unable to find a video or audio track in file %s\n", job.file.c_str() ); - } - if( Timecode::FRAME == format && !isVideoTrack ) - { - // we need a video track for this - return herrf( "unable to find a video track in file %s but chapter file contains frame timestamps\n", job.file.c_str() ); - } - - // get duration and recalculate scale - Timecode refTrackDuration( MP4GetTrackDuration( job.fileHandle, refTrackId ), - MP4GetTrackTimeScale( job.fileHandle, refTrackId ) ); - refTrackDuration.setScale( CHAPTERTIMESCALE ); - - // check for chapters starting after duration of reftrack - for( vector<MP4Chapter_t>::iterator it = chapters.begin(); it != chapters.end(); ) - { - Timecode curr( (*it).duration, CHAPTERTIMESCALE ); - if( refTrackDuration <= curr ) - { - hwarnf( "Chapter '%s' start: %s, playlength of file: %s, chapter cannot be set\n", - (*it).title, curr.svalue.c_str(), refTrackDuration.svalue.c_str() ); - it = chapters.erase( it ); - } - else - { - ++it; - } - } - if( 0 == chapters.size() ) - { - return SUCCESS; - } - - // convert start time into duration - uint32_t framerate = static_cast<uint32_t>( CHAPTERTIMESCALE ); - if( Timecode::FRAME == format ) - { - // get the framerate - MP4SampleId sampleCount = MP4GetTrackNumberOfSamples( job.fileHandle, refTrackId ); - Timecode tmpcd( refTrackDuration.svalue, CHAPTERTIMESCALE ); - framerate = static_cast<uint32_t>( std::ceil( ((double)sampleCount / (double)tmpcd.duration) * CHAPTERTIMESCALE ) ); - } - - for( vector<MP4Chapter_t>::iterator it = chapters.begin(); it != chapters.end(); ++it ) - { - MP4Duration currDur = (*it).duration; - MP4Duration nextDur = chapters.end() == it+1 ? refTrackDuration.duration : (*(it+1)).duration; - - if( Timecode::FRAME == format ) - { - // convert from frame nr to milliseconds - currDur = convertFrameToMillis( (*it).duration, framerate ); - - if( chapters.end() != it+1 ) - { - nextDur = convertFrameToMillis( (*(it+1)).duration, framerate ); - } - } - - (*it).duration = nextDur - currDur; - } - - // now set the chapters - MP4SetChapters( job.fileHandle, &chapters[0], (uint32_t)chapters.size(), _ChapterType ); - - fixQtScale( job.fileHandle ); - job.optimizeApplicable = true; - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Action for removing chapters from the <b>job.file</b> - * - * - * @param job the job to process - * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise - */ -bool -ChapterUtility::actionRemove( JobContext& job ) -{ - ostringstream oss; - oss << "Deleting " << getChapterTypeName( _ChapterType ) << " chapters from file " << '"' << job.file << '"' << endl; - - verbose1f( "%s", oss.str().c_str() ); - if( dryrunAbort() ) - { - return SUCCESS; - } - - job.fileHandle = MP4Modify( job.file.c_str() ); - if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) - { - return herrf( "unable to open for write: %s\n", job.file.c_str() ); - } - - MP4ChapterType chtp = MP4DeleteChapters( job.fileHandle, _ChapterType ); - if( MP4ChapterTypeNone == chtp ) - { - return FAILURE; - } - - fixQtScale( job.fileHandle ); - job.optimizeApplicable = true; - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** process positional argument - * - * @see Utility::utility_job( JobContext& ) - */ -bool -ChapterUtility::utility_job( JobContext& job ) -{ - if( !_action ) - { - return herrf( "no action specified\n" ); - } - - return (this->*_action)( job ); -} - -/////////////////////////////////////////////////////////////////////////////// - -/** process command-line option - * - * @see Utility::utility_option( int, bool& ) - */ -bool -ChapterUtility::utility_option( int code, bool& handled ) -{ - handled = true; - - switch( code ) { - case 'A': - case LC_CHPT_ANY: - _ChapterType = MP4ChapterTypeAny; - break; - - case 'Q': - case LC_CHPT_QT: - _ChapterType = MP4ChapterTypeQt; - break; - - case 'N': - case LC_CHPT_NERO: - _ChapterType = MP4ChapterTypeNero; - break; - - case 'C': - case LC_CHPT_COMMON: - _ChapterFormat = CHPT_FMT_COMMON; - break; - - case 'l': - case LC_CHP_LIST: - _action = &ChapterUtility::actionList; - break; - - case 'e': - case LC_CHP_EVERY: - { - istringstream iss( prog::optarg ); - iss >> _ChaptersEvery; - if( iss.rdstate() != ios::eofbit ) - { - return herrf( "invalid number of seconds: %s\n", prog::optarg ); - } - _action = &ChapterUtility::actionEvery; - break; - } - - case 'x': - _action = &ChapterUtility::actionExport; - break; - - case LC_CHP_EXPORT: - _action = &ChapterUtility::actionExport; - /* currently not supported since the chapters of n input files would be written to one chapter file - _ChapterFile = prog::optarg; - if( _ChapterFile.empty() ) - { - return herrf( "invalid TXT file: empty-string\n" ); - } - */ - break; - - case 'i': - _action = &ChapterUtility::actionImport; - break; - - case LC_CHP_IMPORT: - _action = &ChapterUtility::actionImport; - /* currently not supported since the chapters of n input files would be read from one chapter file - _ChapterFile = prog::optarg; - if( _ChapterFile.empty() ) - { - return herrf( "invalid TXT file: empty-string\n" ); - } - */ - break; - - case 'c': - case LC_CHP_CONVERT: - _action = &ChapterUtility::actionConvert; - break; - - case 'r': - case LC_CHP_REMOVE: - _action = &ChapterUtility::actionRemove; - break; - - default: - handled = false; - break; - } - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Fix a QuickTime/iPod issue with long audio files. - * - * This function checks if the <b>file</b> is a long audio file (more than - * about 6 1/2 hours) and modifies the timescale if necessary to allow - * playback of the file in QuickTime player and on some iPod models. - * - * @param file the opened MP4 file - */ -void -ChapterUtility::fixQtScale(MP4FileHandle file) -{ - // get around a QuickTime/iPod issue with storing the number of samples in a signed 32Bit value - if( INT_MAX < MP4GetDuration(file)) - { - bool isVideoTrack = false; - if( MP4_IS_VALID_TRACK_ID(getReferencingTrack( file, isVideoTrack )) & isVideoTrack ) - { - // if it is a video, everything is different - return; - } - - // timescale too high, lower it - MP4ChangeMovieTimeScale(file, 1000); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Finds a suitable track that can reference a chapter track. - * - * This function returns the first video or audio track that is found - * in the <b>file</b>. - * This track ca be used to reference the QuickTime chapter track. - * - * @param file the opened MP4 file - * @param isVideoTrack receives true if the found track is video, false otherwise - * @return the <b>MP4TrackId</b> of the found track - */ -MP4TrackId -ChapterUtility::getReferencingTrack( MP4FileHandle file, bool& isVideoTrack ) -{ - isVideoTrack = false; - - uint32_t trackCount = MP4GetNumberOfTracks( file ); - if( 0 == trackCount ) - { - return MP4_INVALID_TRACK_ID; - } - - MP4TrackId refTrackId = MP4_INVALID_TRACK_ID; - for( uint32_t i = 0; i < trackCount; ++i ) - { - MP4TrackId id = MP4FindTrackId( file, i ); - const char* type = MP4GetTrackType( file, id ); - if( MP4_IS_VIDEO_TRACK_TYPE( type ) ) - { - refTrackId = id; - isVideoTrack = true; - break; - } - else if( MP4_IS_AUDIO_TRACK_TYPE( type ) ) - { - refTrackId = id; - break; - } - } - - return refTrackId; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Return a human readable representation of a <b>MP4ChapterType</b>. - * - * @param chapterType the chapter type - * @return a string representing the chapter type - */ -string -ChapterUtility::getChapterTypeName( MP4ChapterType chapterType) const -{ - switch( chapterType ) - { - case MP4ChapterTypeQt: - return string( "QuickTime" ); - break; - - case MP4ChapterTypeNero: - return string( "Nero" ); - break; - - case MP4ChapterTypeAny: - return string( "QuickTime and Nero" ); - break; - - default: - return string( "Unknown" ); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Read a file into a buffer. - * - * This function reads the file named by <b>filename</b> into a buffer allocated - * by malloc and returns the pointer to this buffer in <b>buffer</b> and the size - * of this buffer in <b>fileSize</b>. - * - * @param filename the name of the file. - * @param buffer receives a pointer to the created buffer - * @param fileSize reference to a <b>io::StdioFile::Size</b> that receives the size of the file - * @return true if there was an error, false otherwise - */ -bool -ChapterUtility::readChapterFile( const string filename, char** buffer, File::Size& fileSize ) -{ - // open the file - File in( filename, File::MODE_READ ); - File::Size nin; - if( in.open() ) { - return herrf( "opening chapter file '%s' failed: %s\n", filename.c_str(), sys::getLastErrorStr() ); - } - - // get the file size - fileSize = in.size; - if( 0 >= fileSize ) - { - in.close(); - return herrf( "getting size of chapter file '%s' failed: %s\n", filename.c_str(), sys::getLastErrorStr() ); - } - - // allocate a buffer for the file and read the content - char* inBuf = static_cast<char*>( malloc( fileSize+1 ) ); - if( in.read( inBuf, fileSize, nin ) ) - { - in.close(); - return herrf( "reading chapter file '%s' failed: %s\n", filename.c_str(), sys::getLastErrorStr() ); - } - in.close(); - inBuf[fileSize] = 0; - - *buffer = inBuf; - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Read and parse a chapter file. - * - * This function reads and parses a chapter file and returns a vector of - * <b>MP4Chapter_t</b> elements. - * - * @param filename the name of the file. - * @param vector receives a vector of chapters - * @param format receives the <b>Timecode::Format</b> of the timestamps - * @return true if there was an error, false otherwise - */ -bool -ChapterUtility::parseChapterFile( const string filename, vector<MP4Chapter_t>& chapters, Timecode::Format& format ) -{ - // get the content - char * inBuf; - File::Size fileSize; - if( readChapterFile( filename, &inBuf, fileSize ) ) - { - return FAILURE; - } - - // separate the text lines - char* pos = inBuf; - while (pos < inBuf + fileSize) - { - if (*pos == '\n' || *pos == '\r') - { - *pos = 0; - if (pos > inBuf) - { - // remove trailing whitespace - char* tmp = pos-1; - while ((*tmp == ' ' || *tmp == '\t') && tmp > inBuf) - { - *tmp = 0; - tmp--; - } - } - } - pos++; - } - pos = inBuf; - - // check for a BOM - char bom[5] = {0}; - int bomLen = 0; - const unsigned char* uPos = reinterpret_cast<unsigned char*>( pos ); - if( 0xEF == *uPos && 0xBB == *(uPos+1) && 0xBF == *(uPos+2) ) - { - // UTF-8 (we do not need the BOM) - pos += 3; - } - else if( ( 0xFE == *uPos && 0xFF == *(uPos+1) ) // UTF-16 big endian - || ( 0xFF == *uPos && 0xFE == *(uPos+1) ) ) // UTF-16 little endian - { - // store the BOM to prepend the title strings - bom[0] = *pos++; - bom[1] = *pos++; - bomLen = 2; - return herrf( "chapter file '%s' has UTF-16 encoding which is not supported (only UTF-8 is allowed)\n", - filename.c_str() ); - } - else if( ( 0x0 == *uPos && 0x0 == *(uPos+1) && 0xFE == *(uPos+2) && 0xFF == *(uPos+3) ) // UTF-32 big endian - || ( 0xFF == *uPos && *(uPos+1) == 0xFE && *(uPos+2) == 0x0 && 0x0 == *(uPos+3) ) ) // UTF-32 little endian - { - // store the BOM to prepend the title strings - bom[0] = *pos++; - bom[1] = *pos++; - bom[2] = *pos++; - bom[3] = *pos++; - bomLen = 4; - return herrf( "chapter file '%s' has UTF-32 encoding which is not supported (only UTF-8 is allowed)\n", - filename.c_str() ); - } - - // parse the lines - bool failure = false; - uint32_t currentChapter = 0; - FormatState formatState = FMT_STATE_INITIAL; - char* titleStart = 0; - uint32_t titleLen = 0; - char* timeStart = 0; - while( pos < inBuf + fileSize ) - { - if( 0 == *pos || ' ' == *pos || '\t' == *pos ) - { - // uninteresting chars - pos++; - continue; - } - else if( '#' == *pos ) - { - // comment line - pos += strlen( pos ); - continue; - } - else if( isdigit( *pos ) ) - { - // mp4chaps native format: hh:mm:ss.sss <title> - - timeStart = pos; - - // read the title if there is one - titleStart = strchr( timeStart, ' ' ); - if( NULL == titleStart ) - { - titleStart = strchr( timeStart, '\t' ); - } - - if( NULL != titleStart ) - { - *titleStart = 0; - pos = ++titleStart; - - while( ' ' == *titleStart || '\t' == *titleStart ) - { - titleStart++; - } - - titleLen = (uint32_t)strlen( titleStart ); - - // advance to the end of the line - pos = titleStart + 1 + titleLen; - } - else - { - // advance to the end of the line - pos += strlen( pos ); - } - - formatState = FMT_STATE_FINISH; - } -#if defined( _MSC_VER ) - else if( 0 == strnicmp( pos, "CHAPTER", 7 ) ) -#else - else if( 0 == strncasecmp( pos, "CHAPTER", 7 ) ) -#endif - { - // common format: CHAPTERxx=hh:mm:ss.sss\nCHAPTERxxNAME=<title> - - char* equalsPos = strchr( pos+7, '=' ); - if( NULL == equalsPos ) - { - herrf( "Unable to parse line \"%s\"\n", pos ); - failure = true; - break; - } - - *equalsPos = 0; - - char* tlwr = pos; - while( equalsPos != tlwr ) - { - *tlwr = tolower( *tlwr ); - tlwr++; - } - - if( NULL != strstr( pos, "name" ) ) - { - // mark the chapter title - uint32_t chNr = 0; - sscanf( pos, "chapter%dname", &chNr ); - if( chNr != currentChapter ) - { - // different chapter number => different chapter definition pair - if( FMT_STATE_INITIAL != formatState ) - { - herrf( "Chapter lines are not consecutive before line \"%s\"\n", pos ); - failure = true; - break; - } - - currentChapter = chNr; - } - formatState = FMT_STATE_TIME_LINE == formatState ? FMT_STATE_FINISH - : FMT_STATE_TITLE_LINE; - - titleStart = equalsPos + 1; - titleLen = (uint32_t)strlen( titleStart ); - - // advance to the end of the line - pos = titleStart + titleLen; - } - else - { - // mark the chapter start time - uint32_t chNr = 0; - sscanf( pos, "chapter%d", &chNr ); - if( chNr != currentChapter ) - { - // different chapter number => different chapter definition pair - if( FMT_STATE_INITIAL != formatState ) - { - herrf( "Chapter lines are not consecutive at line \"%s\"\n", pos ); - failure = true; - break; - } - - currentChapter = chNr; - } - formatState = FMT_STATE_TITLE_LINE == formatState ? FMT_STATE_FINISH - : FMT_STATE_TIME_LINE; - - timeStart = equalsPos + 1; - - // advance to the end of the line - pos = timeStart + strlen( timeStart ); - } - } - - if( FMT_STATE_FINISH == formatState ) - { - // now we have title and start time - MP4Chapter_t chap; - - strncpy( chap.title, titleStart, min( titleLen, (uint32_t)MP4V2_CHAPTER_TITLE_MAX ) ); - chap.title[titleLen] = 0; - - Timecode tc( 0, CHAPTERTIMESCALE ); - string tm( timeStart ); - if( tc.parse( tm ) ) - { - herrf( "Unable to parse time code from \"%s\"\n", tm.c_str() ); - failure = true; - break; - } - chap.duration = tc.duration; - format = tc.format; - - // ad the chapter to the list - chapters.push_back( chap ); - - // re-initialize - formatState = FMT_STATE_INITIAL; - titleStart = timeStart = NULL; - titleLen = 0; - } - } - free( inBuf ); - if( failure ) - { - return failure; - } - - return SUCCESS; -} - -/////////////////////////////////////////////////////////////////////////////// - -/** Convert from frame to millisecond timestamp. - * - * This function converts a timestamp from hh:mm:ss:ff to hh:mm:ss.sss - * - * @param duration the timestamp in hours:minutes:seconds:frames. - * @param framerate the frames per second - * @return the timestamp in milliseconds - */ -MP4Duration -ChapterUtility::convertFrameToMillis( MP4Duration duration, uint32_t framerate ) -{ - Timecode tc( duration, CHAPTERTIMESCALE ); - if( framerate < tc.subseconds ) - { - uint64_t seconds = tc.subseconds / framerate; - tc.setSeconds( tc.seconds + seconds ); - tc.setSubseconds( (tc.subseconds - (seconds * framerate)) * framerate ); - } - else - { - tc.setSubseconds( tc.subseconds * framerate ); - } - - return tc.duration; -} - -/////////////////////////////////////////////////////////////////////////////// - -}} // namespace mp4v2::util - -/////////////////////////////////////////////////////////////////////////////// - -extern "C" -int main( int argc, char** argv ) -{ - mp4v2::util::ChapterUtility util( argc, argv ); - return util.process(); -} |