You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
439 lines
13 KiB
439 lines
13 KiB
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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<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() );
|
|
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<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 << 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<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() );
|
|
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() );
|
|
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();
|
|
}
|