| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 Kona Blend. |
| // Portions created by Kona Blend are Copyright (C) 2008. |
| // All Rights Reserved. |
| // |
| // Contributors: |
| // Kona Blend, kona8lend@@gmail.com |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "libutil/impl.h" |
| |
| namespace mp4v2 { namespace util { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Utility::Utility( string name_, int argc_, char** argv_ ) |
| : _longOptions ( NULL ) |
| , _name ( name_ ) |
| , _argc ( argc_ ) |
| , _argv ( argv_ ) |
| , _optimize ( false ) |
| , _dryrun ( false ) |
| , _keepgoing ( false ) |
| , _overwrite ( false ) |
| , _force ( false ) |
| , _debug ( 0 ) |
| , _verbosity ( 1 ) |
| , _jobCount ( 0 ) |
| , _debugImplicits ( false ) |
| , _group ( "OPTIONS" ) |
| |
| ,STD_OPTIMIZE( 'z', false, "optimize", false, LC_NONE, "optimize mp4 file after modification" ) |
| ,STD_DRYRUN( 'y', false, "dryrun", false, LC_NONE, "do not actually create or modify any files" ) |
| ,STD_KEEPGOING( 'k', false, "keepgoing", false, LC_NONE, "continue batch processing even after errors" ) |
| ,STD_OVERWRITE( 'o', false, "overwrite", false, LC_NONE, "overwrite existing files when creating" ) |
| ,STD_FORCE( 'f', false, "force", false, LC_NONE, "force overwrite even if file is read-only" ) |
| ,STD_QUIET( 'q', false, "quiet", false, LC_NONE, "equivalent to --verbose 0" ) |
| ,STD_DEBUG( 'd', false, "debug", true, LC_DEBUG, "increase debug or long-option to set NUM", "NUM", |
| // 79-cols, inclusive, max desired width |
| // |----------------------------------------------------------------------------| |
| "\nDEBUG LEVELS (for raw mp4 file I/O)" |
| "\n 0 supressed" |
| "\n 1 add warnings and errors (default)" |
| "\n 2 add table details" |
| "\n 3 add implicits" |
| "\n 4 everything" ) |
| ,STD_VERBOSE( 'v', false, "verbose", true, LC_VERBOSE, "increase verbosity or long-option to set NUM", "NUM", |
| // 79-cols, inclusive, max desired width |
| // |----------------------------------------------------------------------------| |
| "\nVERBOSE LEVELS" |
| "\n 0 warnings and errors" |
| "\n 1 normal informative messages (default)" |
| "\n 2 more informative messages" |
| "\n 3 everything" ) |
| ,STD_HELP( 'h', false, "help", false, LC_HELP, "print brief help or long-option for extended help" ) |
| ,STD_VERSION( 0, false, "version", false, LC_VERSION, "print version information and exit" ) |
| ,STD_VERSIONX( 0, false, "versionx", false, LC_VERSIONX, "print extended version information", "ARG", "", true ) |
| |
| { |
| debugUpdate( 1 ); |
| |
| _usage = "<UNDEFINED>"; |
| _description = "<UNDEFINED>"; |
| _groups.push_back( &_group ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Utility::~Utility() |
| { |
| delete[] _longOptions; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::batch( int argi ) |
| { |
| _jobCount = 0; |
| _jobTotal = _argc - argi; |
| |
| // nothing to be done |
| if( !_jobTotal ) |
| return SUCCESS; |
| |
| bool batchResult = FAILURE; |
| for( int i = argi; i < _argc; i++ ) { |
| bool subResult = FAILURE; |
| try { |
| if( !job( _argv[i] )) { |
| batchResult = SUCCESS; |
| subResult = SUCCESS; |
| } |
| } |
| catch( Exception* x ) { |
| mp4v2::impl::log.errorf(*x); |
| delete x; |
| } |
| |
| if( !_keepgoing && subResult == FAILURE ) |
| return FAILURE; |
| } |
| |
| return batchResult; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::debugUpdate( uint32_t debug ) |
| { |
| MP4LogLevel level; |
| |
| _debug = debug; |
| verbose2f( "debug level: %u\n", _debug ); |
| |
| switch( _debug ) { |
| case 0: |
| level = MP4_LOG_NONE; |
| _debugImplicits = false; |
| break; |
| |
| case 1: |
| level = MP4_LOG_ERROR; |
| _debugImplicits = false; |
| break; |
| |
| case 2: |
| level = MP4_LOG_VERBOSE2; |
| _debugImplicits = false; |
| break; |
| |
| case 3: |
| level = MP4_LOG_VERBOSE2; |
| _debugImplicits = true; |
| break; |
| |
| case 4: |
| default: |
| level = MP4_LOG_VERBOSE4; |
| _debugImplicits = true; |
| break; |
| } |
| |
| MP4LogSetLevel(level); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::dryrunAbort() |
| { |
| if( !_dryrun ) |
| return false; |
| |
| verbose2f( "skipping: dry-run mode enabled\n" ); |
| return true; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::errf( const char* format, ... ) |
| { |
| va_list ap; |
| va_start( ap, format ); |
| vfprintf( stderr, format, ap ); |
| va_end( ap ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::formatGroups() |
| { |
| // determine longest long-option [+space +argname] |
| int longMax = 0; |
| list<Group*>::reverse_iterator ie = _groups.rend(); |
| for( list<Group*>::reverse_iterator it = _groups.rbegin(); it != ie; it++ ) { |
| Group& group = **it; |
| const Group::List::const_iterator ieo = group.options.end(); |
| for( Group::List::const_iterator ito = group.options.begin(); ito != ieo; ito++ ) { |
| const Option& option = **ito; |
| if( option.hidden ) |
| continue; |
| |
| int len = (int)option.lname.length(); |
| if( option.lhasarg ) |
| len += 1 + (int)option.argname.length(); |
| if( len > longMax ) |
| longMax = len; |
| } |
| } |
| |
| // format help output (no line-wrapping yet) |
| ostringstream oss; |
| |
| int groupCount = 0; |
| int optionCount = 0; |
| ie = _groups.rend(); |
| for( list<Group*>::reverse_iterator it = _groups.rbegin(); it != ie; it++, groupCount++ ) { |
| if( groupCount ) |
| oss << '\n'; |
| Group& group = **it; |
| oss << '\n' << group.name; |
| const Group::List::const_iterator ieo = group.options.end(); |
| for( Group::List::const_iterator ito = group.options.begin(); ito != ieo; ito++, optionCount++ ) { |
| const Option& option = **ito; |
| if( option.hidden ) |
| continue; |
| |
| oss << "\n "; |
| |
| if( option.scode == 0 ) |
| oss << " --"; |
| else |
| oss << '-' << option.scode << ", --"; |
| |
| if( option.lhasarg ) { |
| oss << option.lname << ' ' << option.argname; |
| oss << setw( longMax - option.lname.length() - 1 - option.argname.length() ) << ""; |
| } |
| else { |
| oss << setw( longMax ) << left << option.lname; |
| } |
| |
| oss << " "; |
| |
| const string::size_type imax = option.descr.length(); |
| for( string::size_type i = 0; i < imax; i++ ) |
| oss << option.descr[i]; |
| } |
| } |
| |
| _help = oss.str(); |
| |
| // allocate and populate C-style options |
| delete[] _longOptions; |
| _longOptions = new prog::Option[optionCount + 1]; |
| |
| // fill EOL marker |
| _longOptions[optionCount].name = NULL; |
| _longOptions[optionCount].type = prog::Option::NO_ARG; |
| _longOptions[optionCount].flag = 0; |
| _longOptions[optionCount].val = 0; |
| |
| _shortOptions.clear(); |
| |
| int optionIndex = 0; |
| ie = _groups.rend(); |
| for( list<Group*>::reverse_iterator it = _groups.rbegin(); it != ie; it++ ) { |
| Group& group = **it; |
| const Group::List::const_iterator ieo = group.options.end(); |
| for( Group::List::const_iterator ito = group.options.begin(); ito != ieo; ito++, optionIndex++ ) { |
| const Option& a = **ito; |
| prog::Option& b = _longOptions[optionIndex]; |
| |
| b.name = const_cast<char*>(a.lname.c_str()); |
| b.type = a.lhasarg ? prog::Option::REQUIRED_ARG : prog::Option::NO_ARG; |
| b.flag = 0; |
| b.val = (a.lcode == LC_NONE) ? a.scode : a.lcode; |
| |
| if( a.scode != 0 ) { |
| _shortOptions += a.scode; |
| if( a.shasarg ) |
| _shortOptions += ':'; |
| } |
| } |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::job( string arg ) |
| { |
| verbose2f( "job begin: %s\n", arg.c_str() ); |
| |
| // perform job |
| JobContext job( arg ); |
| bool result = FAILURE; |
| try { |
| result = utility_job( job ); |
| } |
| catch( Exception* x ) { |
| mp4v2::impl::log.errorf(*x); |
| delete x; |
| } |
| |
| // close file handle flagged with job |
| if( job.fileHandle != MP4_INVALID_FILE_HANDLE ) { |
| verbose2f( "closing %s\n", job.file.c_str() ); |
| MP4Close( job.fileHandle ); |
| |
| // invoke optimize if flagged |
| if( _optimize && job.optimizeApplicable ) { |
| verbose1f( "optimizing %s\n", job.file.c_str() ); |
| if( !MP4Optimize( job.file.c_str(), NULL )) |
| hwarnf( "optimize failed: %s\n", job.file.c_str() ); |
| } |
| } |
| |
| // free data flagged with job |
| list<void*>::iterator ie = job.tofree.end(); |
| for( list<void*>::iterator it = job.tofree.begin(); it != ie; it++ ) |
| free( *it ); |
| |
| |
| verbose2f( "job end\n" ); |
| _jobCount++; |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::herrf( const char* format, ... ) |
| { |
| va_list ap; |
| va_start( ap, format ); |
| |
| if( _keepgoing ) { |
| fprintf( stdout, "WARNING: " ); |
| vfprintf( stdout, format, ap ); |
| } |
| else { |
| fprintf( stderr, "ERROR: " ); |
| vfprintf( stderr, format, ap ); |
| } |
| |
| va_end( ap ); |
| return FAILURE; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::hwarnf( const char* format, ... ) |
| { |
| fprintf( stdout, "WARNING: " ); |
| va_list ap; |
| va_start( ap, format ); |
| vfprintf( stdout, format, ap ); |
| va_end( ap ); |
| return FAILURE; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::outf( const char* format, ... ) |
| { |
| va_list ap; |
| va_start( ap, format ); |
| vfprintf( stdout, format, ap ); |
| va_end( ap ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::printHelp( bool extended, bool toerr ) |
| { |
| ostringstream oss; |
| oss << "Usage: " << _name << " " << _usage << '\n' << _description << '\n' << _help; |
| |
| if( extended ) { |
| const list<Group*>::reverse_iterator ie = _groups.rend(); |
| for( list<Group*>::reverse_iterator it = _groups.rbegin(); it != ie; it++ ) { |
| Group& group = **it; |
| const Group::List::const_iterator ieo = group.options.end(); |
| for( Group::List::const_iterator ito = group.options.begin(); ito != ieo; ito++ ) { |
| const Option& option = **ito; |
| if( option.help.empty() ) |
| continue; |
| |
| oss << '\n' << option.help; |
| } |
| } |
| } |
| |
| if( toerr ) |
| errf( "%s\n\n", oss.str().c_str() ); |
| else |
| outf( "%s\n\n", oss.str().c_str() ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::printUsage( bool toerr ) |
| { |
| ostringstream oss; |
| oss << "Usage: " << _name << " " << _usage |
| << "\nTry -h for brief help or --help for extended help"; |
| |
| if( toerr ) |
| errf( "%s\n", oss.str().c_str() ); |
| else |
| outf( "%s\n", oss.str().c_str() ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::printVersion( bool extended ) |
| { |
| ostringstream oss; |
| oss << left; |
| |
| if( extended ) { |
| oss << setw(13) << "utility:" << _name |
| << '\n' << setw(13) << "product:" << MP4V2_PROJECT_name |
| << '\n' << setw(13) << "version:" << MP4V2_PROJECT_version |
| << '\n' << setw(13) << "build date:" << MP4V2_PROJECT_build |
| << '\n' |
| << '\n' << setw(18) << "repository URL:" << MP4V2_PROJECT_repo_url |
| << '\n' << setw(18) << "repository root:" << MP4V2_PROJECT_repo_root |
| << '\n' << setw(18) << "repository UUID:" << MP4V2_PROJECT_repo_uuid |
| << '\n' << setw(18) << "repository rev:" << MP4V2_PROJECT_repo_rev |
| << '\n' << setw(18) << "repository date:" << MP4V2_PROJECT_repo_date |
| << '\n' << setw(18) << "repository type:" << MP4V2_PROJECT_repo_type; |
| } |
| else { |
| oss << _name << " - " << MP4V2_PROJECT_name_formal; |
| } |
| |
| outf( "%s\n", oss.str().c_str() ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::process() |
| { |
| bool rv = true; |
| |
| try { |
| rv = process_impl(); |
| } |
| catch( Exception* x ) { |
| _keepgoing = false; |
| mp4v2::impl::log.errorf(*x); |
| delete x; |
| } |
| |
| return rv; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::process_impl() |
| { |
| formatGroups(); |
| |
| // populate code lookup set |
| set<int> codes; |
| const Group::List::const_iterator ie = _group.options.end(); |
| for( Group::List::const_iterator it = _group.options.begin(); it != ie; it++ ) { |
| const Option& option = **it; |
| if( option.scode != 0 ) |
| codes.insert( option.scode ); |
| if( option.lcode != LC_NONE ) |
| codes.insert( option.lcode ); |
| } |
| |
| for( ;; ) { |
| const int code = prog::getOption( _argc, _argv, _shortOptions.c_str(), _longOptions, NULL ); |
| if( code == -1 ) |
| break; |
| |
| bool handled = false; |
| if( utility_option( code, handled )) |
| return FAILURE; |
| if( handled ) |
| continue; |
| |
| if( codes.find( code ) == codes.end() ) |
| continue; |
| |
| switch( code ) { |
| case 'z': |
| _optimize = true; |
| break; |
| |
| case 'y': |
| _dryrun = true; |
| break; |
| |
| case 'k': |
| _keepgoing = true; |
| break; |
| |
| case 'o': |
| _overwrite = true; |
| break; |
| |
| case 'f': |
| _force = true; |
| break; |
| |
| case 'q': |
| _verbosity = 0; |
| debugUpdate( 0 ); |
| break; |
| |
| case 'v': |
| _verbosity++; |
| break; |
| |
| case 'd': |
| debugUpdate( _debug + 1 ); |
| break; |
| |
| case 'h': |
| printHelp( false, false ); |
| return SUCCESS; |
| |
| case LC_DEBUG: |
| debugUpdate( std::strtoul( prog::optarg, NULL, 0 ) ); |
| break; |
| |
| case LC_VERBOSE: |
| { |
| const uint32_t level = std::strtoul( prog::optarg, NULL, 0 ); |
| _verbosity = ( level < 4 ) ? level : 3; |
| break; |
| } |
| |
| case LC_HELP: |
| printHelp( true, false ); |
| return SUCCESS; |
| |
| case LC_VERSION: |
| printVersion( false ); |
| return SUCCESS; |
| |
| case LC_VERSIONX: |
| printVersion( true ); |
| return SUCCESS; |
| |
| default: |
| printUsage( true ); |
| return FAILURE; |
| } |
| } |
| |
| if( !(prog::optind < _argc) ) { |
| printUsage( true ); |
| return FAILURE; |
| } |
| |
| const bool result = batch( prog::optind ); |
| verbose2f( "exit code %d\n", result ); |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Utility::openFileForWriting( io::File& file ) |
| { |
| // simple case is file does not exist |
| if( !io::FileSystem::exists( file.name )) { |
| if( file.open() ) |
| return herrf( "unable to open %s for write: %s\n", file.name.c_str(), sys::getLastErrorStr() ); |
| return SUCCESS; |
| } |
| |
| // fail if overwrite is not enabled |
| if( !_overwrite ) |
| return herrf( "file already exists: %s\n", file.name.c_str() ); |
| |
| // only overwrite if it is a file |
| if( !io::FileSystem::isFile( file.name )) |
| return herrf( "cannot overwrite non-file: %s\n", file.name.c_str() ); |
| |
| // first attemp to re-open/truncate so as to keep any file perms |
| if( !file.open() ) |
| return SUCCESS; |
| |
| // fail if force is not enabled |
| if( !_force ) |
| return herrf( "unable to overwrite file: %s\n", file.name.c_str() ); |
| |
| // first attempt to open, truncating file |
| if( !file.open() ) |
| return SUCCESS; |
| |
| // nuke file |
| if( ::remove( file.name.c_str() )) |
| return herrf( "unable to remove %s: %s\n", file.name.c_str(), sys::getLastErrorStr() ); |
| |
| // final effort |
| if( !file.open() ) |
| return SUCCESS; |
| |
| return herrf( "unable to open %s for write: %s\n", file.name.c_str(), sys::getLastErrorStr() ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::verbose( uint32_t level, const char* format, va_list ap ) |
| { |
| if( level > _verbosity ) |
| return; |
| vfprintf( stdout, format, ap ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::verbose1f( const char* format, ... ) |
| { |
| va_list ap; |
| va_start( ap, format ); |
| verbose( 1, format, ap ); |
| va_end( ap ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::verbose2f( const char* format, ... ) |
| { |
| va_list ap; |
| va_start( ap, format ); |
| verbose( 2, format, ap ); |
| va_end( ap ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::verbose3f( const char* format, ... ) |
| { |
| va_list ap; |
| va_start( ap, format ); |
| verbose( 3, format, ap ); |
| va_end( ap ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| const bool Utility::SUCCESS = false; |
| const bool Utility::FAILURE = true; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Utility::Group::Group( string name_ ) |
| : name ( name_ ) |
| , options ( _options ) |
| { |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Utility::Group::~Group() |
| { |
| const List::iterator ie = _optionsDelete.end(); |
| for( List::iterator it = _optionsDelete.begin(); it != ie; it++ ) |
| delete *it; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::Group::add( const Option& option ) |
| { |
| _options.push_back( &option ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::Group::add( |
| char scode, |
| bool shasarg, |
| string lname, |
| bool lhasarg, |
| uint32_t lcode, |
| string descr, |
| string argname, |
| string help, |
| bool hidden ) |
| { |
| Option* o = new Option( scode, shasarg, lname, lhasarg, lcode, descr, argname, help, hidden ); |
| _options.push_back( o ); |
| _optionsDelete.push_back( o ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Utility::Group::add( |
| string lname, |
| bool lhasarg, |
| uint32_t lcode, |
| string descr, |
| string argname, |
| string help, |
| bool hidden ) |
| { |
| add( 0, false, lname, lhasarg, lcode, descr, argname, help, hidden ); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Utility::Option::Option( |
| char scode_, |
| bool shasarg_, |
| string lname_, |
| bool lhasarg_, |
| uint32_t lcode_, |
| string descr_, |
| string argname_, |
| string help_, |
| bool hidden_ ) |
| : scode ( scode_ ) |
| , shasarg ( shasarg_ ) |
| , lname ( lname_ ) |
| , lhasarg ( lhasarg_ ) |
| , lcode ( lcode_ ) |
| , descr ( descr_ ) |
| , argname ( argname_ ) |
| , help ( help_ ) |
| , hidden ( hidden_ ) |
| { |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Utility::JobContext::JobContext( string file_ ) |
| : file ( file_ ) |
| , fileHandle ( MP4_INVALID_FILE_HANDLE ) |
| , optimizeApplicable ( false ) |
| { |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| }} // namespace mp4v2::util |