/////////////////////////////////////////////////////////////////////////////// // // 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. // Portions created by David Byron are Copyright (C) 2010. // All Rights Reserved. // // Contributors: // Kona Blend, kona8lend@@gmail.com // David Byron, dbyron@dbyron.com // /////////////////////////////////////////////////////////////////////////////// #include "util/impl.h" namespace mp4v2 { namespace util { using namespace itmf; /////////////////////////////////////////////////////////////////////////////// class ArtUtility : public Utility { private: enum ArtLongCode { LC_ART_ANY = _LC_MAX, LC_ART_INDEX, LC_LIST, LC_ADD, LC_REMOVE, LC_REPLACE, LC_EXTRACT, }; public: ArtUtility( int, char** ); protected: // delegates implementation bool utility_option( int, bool& ); bool utility_job( JobContext& ); private: struct ArtType { string name; string ext; vector cwarns; // compatibility warnings string cerror; // compatibility error }; bool actionList ( JobContext& ); bool actionAdd ( JobContext& ); bool actionRemove ( JobContext& ); bool actionReplace ( JobContext& ); bool actionExtract ( JobContext& ); bool extractSingle( JobContext&, const CoverArtBox::Item&, uint32_t ); private: Group _actionGroup; Group _parmGroup; bool (ArtUtility::*_action)( JobContext& ); string _artImageFile; uint32_t _artFilter; }; /////////////////////////////////////////////////////////////////////////////// ArtUtility::ArtUtility( int argc, char** argv ) : Utility ( "mp4art", argc, argv ) , _actionGroup ( "ACTIONS" ) , _parmGroup ( "ACTION PARAMETERS" ) , _action ( NULL ) , _artFilter ( numeric_limits::max() ) { // 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( "art-any", false, LC_ART_ANY, "act on all covr-boxes (default)" ); _parmGroup.add( "art-index", true, LC_ART_INDEX, "act on covr-box index IDX", "IDX" ); _groups.push_back( &_parmGroup ); _actionGroup.add( "list", false, LC_LIST, "list all covr-boxes" ); _actionGroup.add( "add", true, LC_ADD, "add covr-box from IMG file", "IMG" ); _actionGroup.add( "replace", true, LC_REPLACE, "replace covr-box with IMG file", "IMG" ); _actionGroup.add( "remove", false, LC_REMOVE, "remove covr-box" ); _actionGroup.add( "extract", false, LC_EXTRACT, "extract covr-box" ); _groups.push_back( &_actionGroup ); _usage = "[OPTION]... ACTION file..."; _description = // 79-cols, inclusive, max desired width // |----------------------------------------------------------------------------| "\nFor each mp4 (m4a) file specified, perform the specified ACTION. An action" "\nmust be specified. Some options are not applicable for some actions."; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::actionAdd( JobContext& job ) { File in( _artImageFile, File::MODE_READ ); if( in.open() ) return herrf( "unable to open %s for read: %s\n", _artImageFile.c_str(), sys::getLastErrorStr() ); const uint32_t max = numeric_limits::max(); if( in.size > max ) return herrf( "file too large: %s (exceeds %u bytes)\n", _artImageFile.c_str(), max ); CoverArtBox::Item item; item.size = static_cast( in.size ); item.buffer = static_cast( malloc( item.size )); item.autofree = true; File::Size nin; if( in.read( item.buffer, item.size, nin )) return herrf( "read failed: %s\n", _artImageFile.c_str() ); in.close(); verbose1f( "adding %s -> %s\n", _artImageFile.c_str(), job.file.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() ); if( CoverArtBox::add( job.fileHandle, item )) return herrf( "unable to add covr-box\n" ); return SUCCESS; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::actionExtract( 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() ); // single-mode if( _artFilter != numeric_limits::max() ) { CoverArtBox::Item item; if( CoverArtBox::get( job.fileHandle, item, _artFilter )) return herrf( "unable to retrieve covr-box (index=%d): %s\n", _artFilter, job.file.c_str() ); return extractSingle( job, item, _artFilter ); } // wildcard-mode CoverArtBox::ItemList items; if( CoverArtBox::list( job.fileHandle, items )) return herrf( "unable to fetch list of covr-box: %s\n", job.file.c_str() ); bool onesuccess = false; const CoverArtBox::ItemList::size_type max = items.size(); for( CoverArtBox::ItemList::size_type i = 0; i < max; i++ ) { bool rv = extractSingle( job, items[i], (uint32_t)i ); if( !rv ) onesuccess = true; if( !_keepgoing && rv ) return FAILURE; } return _keepgoing ? onesuccess : SUCCESS; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::actionList( JobContext& job ) { ostringstream report; const int widx = 3; const int wsize = 8; const int wtype = 9; const string sep = " "; if( _jobCount == 0 ) { report << setw(widx) << right << "IDX" << left << sep << setw(wsize) << right << "BYTES" << left << sep << setw(8) << "CRC32" << sep << setw(wtype) << "TYPE" << sep << setw(0) << "FILE" << '\n'; report << setfill('-') << setw(70) << "" << setfill(' ') << '\n'; } 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() ); CoverArtBox::ItemList items; if( CoverArtBox::list( job.fileHandle, items )) return herrf( "unable to get list of covr-box: %s\n", job.file.c_str() ); int line = 0; const CoverArtBox::ItemList::size_type max = items.size(); for( CoverArtBox::ItemList::size_type i = 0; i < max; i++ ) { if( _artFilter != numeric_limits::max() && _artFilter != i ) continue; CoverArtBox::Item& item = items[i]; const uint32_t crc = crc32( item.buffer, item.size ); report << setw(widx) << right << i << sep << setw(wsize) << item.size << sep << setw(8) << setfill('0') << hex << crc << setfill(' ') << dec << sep << setw(wtype) << left << enumBasicType.toString( item.type ); if( line++ == 0 ) report << sep << setw(0) << job.file; report << '\n'; } verbose1f( "%s", report.str().c_str() ); return SUCCESS; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::actionRemove( JobContext& job ) { 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() ); if( _artFilter == numeric_limits::max() ) verbose1f( "removing covr-box (all) from %s\n", job.file.c_str() ); else verbose1f( "removing covr-box (index=%d) from %s\n", _artFilter, job.file.c_str() ); if( dryrunAbort() ) return SUCCESS; if( CoverArtBox::remove( job.fileHandle, _artFilter )) return herrf( "remove failed\n" ); return SUCCESS; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::actionReplace( JobContext& job ) { File in( _artImageFile, File::MODE_READ ); if( in.open() ) return herrf( "unable to open %s for read: %s\n", _artImageFile.c_str(), sys::getLastErrorStr() ); const uint32_t max = numeric_limits::max(); if( in.size > max ) return herrf( "file too large: %s (exceeds %u bytes)\n", _artImageFile.c_str(), max ); CoverArtBox::Item item; item.size = static_cast( in.size ); item.buffer = static_cast( malloc( item.size )); item.autofree = true; File::Size nin; if( in.read( item.buffer, item.size, nin )) return herrf( "read failed: %s\n", _artImageFile.c_str() ); in.close(); if( _artFilter == numeric_limits::max() ) verbose1f( "replacing %s -> %s (all)\n", _artImageFile.c_str(), job.file.c_str() ); else verbose1f( "replacing %s -> %s (index=%d)\n", _artImageFile.c_str(), job.file.c_str(), _artFilter ); 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() ); if( CoverArtBox::set( job.fileHandle, item, _artFilter )) return herrf( "unable to add covr-box: %s\n", job.file.c_str() ); return SUCCESS; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::extractSingle( JobContext& job, const CoverArtBox::Item& item, uint32_t index ) { // compute out filename string out_name = job.file; FileSystem::pathnameStripExtension( out_name ); ostringstream oss; oss << out_name << ".art[" << index << ']'; // if implicit we try to determine type by inspecting data BasicType bt = item.type; if( bt == BT_IMPLICIT ) bt = computeBasicType( item.buffer, item.size ); // add file extension appropriate for known covr-box types switch( bt ) { case BT_GIF: oss << ".gif"; break; case BT_JPEG: oss << ".jpg"; break; case BT_PNG: oss << ".png"; break; case BT_BMP: oss << ".bmp"; break; default: oss << ".dat"; break; } out_name = oss.str(); verbose1f( "extracting %s (index=%d) -> %s\n", job.file.c_str(), index, out_name.c_str() ); if( dryrunAbort() ) return SUCCESS; File out( out_name, File::MODE_CREATE ); if( openFileForWriting( out )) return FAILURE; File::Size nout; if( out.write( item.buffer, item.size, nout )) return herrf( "write failed: %s\n", out_name.c_str() ); out.close(); return SUCCESS; } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::utility_job( JobContext& job ) { if( !_action ) return herrf( "no action specified\n" ); return (this->*_action)( job ); } /////////////////////////////////////////////////////////////////////////////// bool ArtUtility::utility_option( int code, bool& handled ) { handled = true; switch( code ) { case LC_ART_ANY: _artFilter = numeric_limits::max(); break; case LC_ART_INDEX: { istringstream iss( prog::optarg ); iss >> _artFilter; if( iss.rdstate() != ios::eofbit ) return herrf( "invalid cover-art index: %s\n", prog::optarg ); break; } case LC_LIST: _action = &ArtUtility::actionList; break; case LC_ADD: _action = &ArtUtility::actionAdd; _artImageFile = prog::optarg; if( _artImageFile.empty() ) return herrf( "invalid image file: empty-string\n" ); break; case LC_REMOVE: _action = &ArtUtility::actionRemove; break; case LC_REPLACE: _action = &ArtUtility::actionReplace; _artImageFile = prog::optarg; if( _artImageFile.empty() ) return herrf( "invalid image file: empty-string\n" ); break; case LC_EXTRACT: _action = &ArtUtility::actionExtract; break; default: handled = false; break; } return SUCCESS; } /////////////////////////////////////////////////////////////////////////////// }} // namespace mp4v2::util /////////////////////////////////////////////////////////////////////////////// extern "C" int main( int argc, char** argv ) { mp4v2::util::ArtUtility util( argc, argv ); return util.process(); }