diff options
Diffstat (limited to 'util/mp4art.cpp')
-rw-r--r-- | util/mp4art.cpp | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/util/mp4art.cpp b/util/mp4art.cpp new file mode 100644 index 0000000..3dc29af --- /dev/null +++ b/util/mp4art.cpp @@ -0,0 +1,436 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// 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 "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<string> 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<uint32_t>::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<uint32_t>::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<uint32_t>( in.size ); + item.buffer = static_cast<uint8_t*>( 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(), _debugVerbosity ); + 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(), _debugVerbosity ); + 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<uint32_t>::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], 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(), _debugVerbosity ); + 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<uint32_t>::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(), _debugVerbosity ); + if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) + return herrf( "unable to open for write: %s\n", job.file.c_str() ); + + if( _artFilter == numeric_limits<uint32_t>::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<uint32_t>::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<uint32_t>( in.size ); + item.buffer = static_cast<uint8_t*>( 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<uint32_t>::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(), _debugVerbosity ); + 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<uint32_t>::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(); +} |