| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| std::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 ( std::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 = std::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() ); |
| 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 != std::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], (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 << std::setw(widx) << right << "IDX" << left << sep |
| << std::setw(wsize) << right << "BYTES" << left << sep |
| << std::setw(8) << "CRC32" << sep << std::setw(wtype) << "TYPE" |
| << sep << std::setw(0) << "FILE" << '\n'; |
| |
| report << std::setfill('-') << std::setw(70) << "" << std::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 != std::numeric_limits<uint32_t>::max() && _artFilter != i) |
| continue; |
| |
| CoverArtBox::Item& item = items[i]; |
| const uint32_t crc = crc32(item.buffer, item.size); |
| |
| report << std::setw(widx) << right << i << sep << std::setw(wsize) |
| << item.size << sep << std::setw(8) << std::setfill('0') |
| << std::hex << crc << std::setfill(' ') << std::dec << sep |
| << std::setw(wtype) << left << enumBasicType.toString(item.type); |
| |
| if (line++ == 0) report << sep << std::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 == std::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 = std::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 == std::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() ); |
| 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 = std::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(); |
| } |