aboutsummaryrefslogtreecommitdiff
path: root/util/mp4art.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'util/mp4art.cpp')
-rw-r--r--util/mp4art.cpp436
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();
+}