diff options
Diffstat (limited to 'util/mp4tags.cpp')
-rw-r--r-- | util/mp4tags.cpp | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/util/mp4tags.cpp b/util/mp4tags.cpp new file mode 100644 index 0000000..04e712e --- /dev/null +++ b/util/mp4tags.cpp @@ -0,0 +1,493 @@ +/* mp4tags -- tool to set iTunes-compatible metadata tags + * + * 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. + * + * Contributed to MPEG4IP + * by Christopher League <league@contrapunctus.net> + */ + +#include "util/impl.h" + +using namespace mp4v2::util; + +/////////////////////////////////////////////////////////////////////////////// + +/* One-letter options -- if you want to rearrange these, change them + here, immediately below in OPT_STRING, and in the help text. */ +#define OPT_HELP 0x01ff +#define OPT_VERSION 0x02ff +#define OPT_ALBUM 'A' +#define OPT_ARTIST 'a' +#define OPT_TEMPO 'b' +#define OPT_COMMENT 'c' +#define OPT_COPYRIGHT 'C' +#define OPT_DISK 'd' +#define OPT_DISKS 'D' +#define OPT_ENCODEDBY 'e' +#define OPT_TOOL 'E' +#define OPT_GENRE 'g' +#define OPT_GROUPING 'G' +#define OPT_HD 'H' +#define OPT_MEDIA_TYPE 'i' +#define OPT_CNID 'I' +#define OPT_LONGDESC 'l' +#define OPT_LYRICS 'L' +#define OPT_DESCRIPTION 'm' +#define OPT_TVEPISODE 'M' +#define OPT_TVSEASON 'n' +#define OPT_TVNETWORK 'N' +#define OPT_TVEPISODEID 'o' +#define OPT_PICTURE 'P' +#define OPT_NAME 's' +#define OPT_TVSHOW 'S' +#define OPT_TRACK 't' +#define OPT_TRACKS 'T' +#define OPT_COMPOSER 'w' +#define OPT_RELEASEDATE 'y' +#define OPT_REMOVE 'r' +#define OPT_ALBUM_ARTIST 'R' + +#define OPT_STRING "A:a:b:c:C:d:D:e:E:g:G:H:i:I:l:L:m:M:n:N:o:P:s:S:t:T:w:y:r:R:" + +#define ELEMENT_OF(x,i) x[int(i)] + +static const char* const help_text = + "OPTION... FILE...\n" + "Adds or modifies iTunes-compatible tags on MP4 files.\n" + "\n" + " -help Display this help text and exit\n" + " -version Display version information and exit\n" + " -A, -album STR Set the album title\n" + " -a, -artist STR Set the artist information\n" + " -b, -tempo NUM Set the tempo (beats per minute)\n" + " -c, -comment STR Set a general comment\n" + " -C, -copyright STR Set the copyright information\n" + " -d, -disk NUM Set the disk number\n" + " -D, -disks NUM Set the number of disks\n" + " -e, -encodedby STR Set the name of the person or company who encoded the file\n" + " -E, -tool STR Set the software used for encoding\n" + " -g, -genre STR Set the genre name\n" + " -G, -grouping STR Set the grouping name\n" + " -H, -hdvideo NUM Set the HD flag (1\\0)\n" + " -i, -type STR Set the Media Type(tvshow, movie, music, ...)\n" + " -I, -cnid NUM Set the cnID\n" + " -l, -longdesc NUM Set the short description\n" + " -L, -lyrics NUM Set the lyrics\n" + " -m, -description STR Set the short description\n" + " -M, -episode NUM Set the episode number\n" + " -n, -season NUM Set the season number\n" + " -N, -network STR Set the TV network\n" + " -o, -episodeid STR Set the TV episode ID\n" + " -P, -picture PTH Set the picture as a .png\n" + " -s, -song STR Set the song title\n" + " -S -show STR Set the TV show\n" + " -t, -track NUM Set the track number\n" + " -T, -tracks NUM Set the number of tracks\n" + " -w, -writer STR Set the composer information\n" + " -y, -year NUM Set the release date\n" + " -R, -albumartist STR Set the album artist\n" + " -r, -remove STR Remove tags by code (e.g. \"-r cs\"\n" + " removes the comment and song tags)"; + +extern "C" int + main( int argc, char** argv ) +{ + const prog::Option long_options[] = { + { "help", prog::Option::NO_ARG, 0, OPT_HELP }, + { "version", prog::Option::NO_ARG, 0, OPT_VERSION }, + { "album", prog::Option::REQUIRED_ARG, 0, OPT_ALBUM }, + { "artist", prog::Option::REQUIRED_ARG, 0, OPT_ARTIST }, + { "comment", prog::Option::REQUIRED_ARG, 0, OPT_COMMENT }, + { "copyright", prog::Option::REQUIRED_ARG, 0, OPT_COPYRIGHT }, + { "disk", prog::Option::REQUIRED_ARG, 0, OPT_DISK }, + { "disks", prog::Option::REQUIRED_ARG, 0, OPT_DISKS }, + { "encodedby", prog::Option::REQUIRED_ARG, 0, OPT_ENCODEDBY }, + { "tool", prog::Option::REQUIRED_ARG, 0, OPT_TOOL }, + { "genre", prog::Option::REQUIRED_ARG, 0, OPT_GENRE }, + { "grouping", prog::Option::REQUIRED_ARG, 0, OPT_GROUPING }, + { "hdvideo", prog::Option::REQUIRED_ARG, 0, OPT_HD }, + { "type", prog::Option::REQUIRED_ARG, 0, OPT_MEDIA_TYPE }, + { "cnid", prog::Option::REQUIRED_ARG, 0, OPT_CNID }, + { "longdesc", prog::Option::REQUIRED_ARG, 0, OPT_LONGDESC }, + { "lyrics", prog::Option::REQUIRED_ARG, 0, OPT_LYRICS }, + { "description", prog::Option::REQUIRED_ARG, 0, OPT_DESCRIPTION }, + { "episode", prog::Option::REQUIRED_ARG, 0, OPT_TVEPISODE }, + { "season", prog::Option::REQUIRED_ARG, 0, OPT_TVSEASON }, + { "network", prog::Option::REQUIRED_ARG, 0, OPT_TVNETWORK }, + { "episodeid", prog::Option::REQUIRED_ARG, 0, OPT_TVEPISODEID }, + { "picture", prog::Option::REQUIRED_ARG, 0, OPT_PICTURE }, + { "song", prog::Option::REQUIRED_ARG, 0, OPT_NAME }, + { "show", prog::Option::REQUIRED_ARG, 0, OPT_TVSHOW }, + { "tempo", prog::Option::REQUIRED_ARG, 0, OPT_TEMPO }, + { "track", prog::Option::REQUIRED_ARG, 0, OPT_TRACK }, + { "tracks", prog::Option::REQUIRED_ARG, 0, OPT_TRACKS }, + { "writer", prog::Option::REQUIRED_ARG, 0, OPT_COMPOSER }, + { "year", prog::Option::REQUIRED_ARG, 0, OPT_RELEASEDATE }, + { "remove", prog::Option::REQUIRED_ARG, 0, OPT_REMOVE }, + { "albumartist", prog::Option::REQUIRED_ARG, 0, OPT_ALBUM_ARTIST }, + { NULL, prog::Option::NO_ARG, 0, 0 } + }; + + /* Sparse arrays of tag data: some space is wasted, but it's more + convenient to say tags[OPT_SONG] than to enumerate all the + metadata types (again) as a struct. */ + const char *tags[UCHAR_MAX]; + int nums[UCHAR_MAX]; + + memset( tags, 0, sizeof( tags ) ); + memset( nums, 0, sizeof( nums ) ); + + /* Any modifications requested? */ + int mods = 0; + + /* Option-processing loop. */ + int c = prog::getOptionSingle( argc, argv, OPT_STRING, long_options, NULL ); + while ( c != -1 ) { + int r = 2; + switch ( c ) { + /* getopt() returns '?' if there was an error. It already + printed the error message, so just return. */ + case '?': + return 1; + + /* Help and version requests handled here. */ + case OPT_HELP: + fprintf( stderr, "usage %s %s\n", argv[0], help_text ); + return 0; + case OPT_VERSION: + fprintf( stderr, "%s - %s\n", argv[0], MP4V2_PROJECT_name_formal ); + return 0; + + /* Numeric arguments: convert them using sscanf(). */ + case OPT_DISK: + case OPT_DISKS: + case OPT_TRACK: + case OPT_TRACKS: + case OPT_HD: + case OPT_CNID: + case OPT_TVEPISODE: + case OPT_TVSEASON: + case OPT_TEMPO: + r = sscanf( prog::optarg, "%d", &nums[c] ); + if ( r < 1 ) { + fprintf( stderr, "%s: option requires numeric argument -- %c\n", + argv[0], c ); + return 2; + } + /* Break not, lest ye be broken. :) */ + /* All arguments: all valid options end up here, and we just + stuff the string pointer into the tags[] array. */ + default: + tags[c] = prog::optarg; + mods++; + } /* end switch */ + + c = prog::getOptionSingle( argc, argv, OPT_STRING, long_options, NULL ); + } /* end while */ + + /* Check that we have at least one non-option argument */ + if ( ( argc - prog::optind ) < 1 ) { + fprintf( stderr, + "%s: You must specify at least one MP4 file.\n", + argv[0] ); + fprintf( stderr, "usage %s %s\n", argv[0], help_text ); + return 3; + } + + /* Check that we have at least one requested modification. Probably + it's useful instead to print the metadata if no modifications are + requested? */ + if ( !mods ) { + fprintf( stderr, + "%s: You must specify at least one tag modification.\n", + argv[0] ); + fprintf( stderr, "usage %s %s\n", argv[0], help_text ); + return 4; + } + + /* Loop through the non-option arguments, and modify the tags as + requested. */ + while ( prog::optind < argc ) { + char *mp4 = argv[prog::optind++]; + + MP4FileHandle h = MP4Modify( mp4 ); + if ( h == MP4_INVALID_FILE_HANDLE ) { + fprintf( stderr, "Could not open '%s'... aborting\n", mp4 ); + return 5; + } + /* Read out the existing metadata */ + const MP4Tags* mdata = MP4TagsAlloc(); + MP4TagsFetch( mdata, h ); + + /* Remove any tags */ + if ( ELEMENT_OF(tags,OPT_REMOVE) ) { + for ( const char *p = ELEMENT_OF(tags,OPT_REMOVE); *p; p++ ) { + switch ( *p ) { + case OPT_ALBUM: + MP4TagsSetAlbum( mdata, NULL ); + break; + case OPT_ARTIST: + MP4TagsSetArtist( mdata, NULL ); + break; + case OPT_COMMENT: + MP4TagsSetComments( mdata, NULL ); + break; + case OPT_COPYRIGHT: + MP4TagsSetCopyright( mdata, NULL ); + break; + case OPT_DISK: + MP4TagsSetDisk( mdata, NULL ); + break; + case OPT_DISKS: + MP4TagsSetDisk( mdata, NULL ); + break; + case OPT_ENCODEDBY: + MP4TagsSetEncodedBy( mdata, NULL ); + break; + case OPT_TOOL: + MP4TagsSetEncodingTool( mdata, NULL ); + break; + case OPT_GENRE: + MP4TagsSetGenre( mdata, NULL ); + break; + case OPT_GROUPING: + MP4TagsSetGrouping( mdata, NULL ); + break; + case OPT_HD: + MP4TagsSetHDVideo( mdata, NULL ); + break; + case OPT_CNID: + MP4TagsSetCNID( mdata, NULL ); + break; + case OPT_LONGDESC: + MP4TagsSetLongDescription( mdata, NULL ); + break; + case OPT_LYRICS: + MP4TagsSetLyrics( mdata, NULL ); + break; + case OPT_MEDIA_TYPE: + MP4TagsSetMediaType( mdata, NULL ); + break; + case OPT_DESCRIPTION: + MP4TagsSetDescription( mdata, NULL ); + break; + case OPT_TVEPISODE: + MP4TagsSetTVEpisode( mdata, NULL ); + break; + case OPT_TVSEASON: + MP4TagsSetTVSeason( mdata, NULL ); + break; + case OPT_TVNETWORK: + MP4TagsSetTVNetwork( mdata, NULL ); + break; + case OPT_TVEPISODEID: + MP4TagsSetTVEpisodeID( mdata, NULL ); + break; + case OPT_NAME: + MP4TagsSetName( mdata, NULL ); + break; + case OPT_TVSHOW: + MP4TagsSetTVShow( mdata, NULL ); + break; + case OPT_COMPOSER: + MP4TagsSetComposer( mdata, NULL ); + break; + case OPT_RELEASEDATE: + MP4TagsSetReleaseDate( mdata, NULL ); + break; + case OPT_TEMPO: + MP4TagsSetTempo( mdata, NULL ); + break; + case OPT_TRACK: + MP4TagsSetTrack( mdata, NULL ); + break; + case OPT_TRACKS: + MP4TagsSetTrack( mdata, NULL ); + break; + case OPT_PICTURE: + if( mdata->artworkCount ) + MP4TagsRemoveArtwork( mdata, 0 ); + break; + case OPT_ALBUM_ARTIST: + MP4TagsSetAlbumArtist( mdata, NULL ); + break ; + } + } + } + + /* Track/disk numbers need to be set all at once, but we'd like to + allow users to just specify -T 12 to indicate that all existing + track numbers are out of 12. This means we need to look up the + current info if it is not being set. */ + + if ( ELEMENT_OF(tags,OPT_TRACK) || ELEMENT_OF(tags,OPT_TRACKS) ) { + MP4TagTrack tt; + tt.index = 0; + tt.total = 0; + + if( mdata->track ) { + tt.index = mdata->track->index; + tt.total = mdata->track->total; + } + + if( ELEMENT_OF(tags,OPT_TRACK) ) + tt.index = ELEMENT_OF(nums,OPT_TRACK); + if( ELEMENT_OF(tags,OPT_TRACKS) ) + tt.total = ELEMENT_OF(nums,OPT_TRACKS); + + MP4TagsSetTrack( mdata, &tt ); + } + + if ( ELEMENT_OF(tags,OPT_TRACK) || ELEMENT_OF(tags,OPT_TRACKS) ) { + MP4TagDisk td; + td.index = 0; + td.total = 0; + + if( mdata->disk ) { + td.index = mdata->disk->index; + td.total = mdata->disk->total; + } + + if( ELEMENT_OF(tags,OPT_DISK) ) + td.index = ELEMENT_OF(nums,OPT_DISK); + if( ELEMENT_OF(tags,OPT_DISKS) ) + td.total = ELEMENT_OF(nums,OPT_DISKS); + + MP4TagsSetDisk( mdata, &td ); + } + + /* Set the other relevant attributes */ + for ( int i = 0; i < UCHAR_MAX; i++ ) { + if ( tags[i] ) { + switch ( i ) { + case OPT_ALBUM: + MP4TagsSetAlbum( mdata, tags[i] ); + break; + case OPT_ARTIST: + MP4TagsSetArtist( mdata, tags[i] ); + break; + case OPT_COMMENT: + MP4TagsSetComments( mdata, tags[i] ); + break; + case OPT_COPYRIGHT: + MP4TagsSetCopyright( mdata, tags[i] ); + break; + case OPT_ENCODEDBY: + MP4TagsSetEncodedBy( mdata, tags[i] ); + break; + case OPT_TOOL: + MP4TagsSetEncodingTool( mdata, tags[i] ); + break; + case OPT_GENRE: + MP4TagsSetGenre( mdata, tags[i] ); + break; + case OPT_GROUPING: + MP4TagsSetGrouping( mdata, tags[i] ); + break; + case OPT_HD: + { + uint8_t value = static_cast<uint8_t>( nums[i] ); + MP4TagsSetHDVideo( mdata, &value ); + break; + } + case OPT_CNID: + { + uint32_t value = static_cast<uint32_t>( nums[i] ); + MP4TagsSetCNID( mdata, &value ); + break; + } + case OPT_LONGDESC: + MP4TagsSetLongDescription( mdata, tags[i] ); + break; + case OPT_LYRICS: + MP4TagsSetLyrics( mdata, tags[i] ); + break; + case OPT_MEDIA_TYPE: + { + uint8_t st = static_cast<uint8_t>( itmf::enumStikType.toType( tags[i] ) ) ; + MP4TagsSetMediaType( mdata, &st ); + break; + } + case OPT_DESCRIPTION: + MP4TagsSetDescription( mdata, tags[i] ); + break; + case OPT_TVEPISODE: + { + uint32_t value = static_cast<uint32_t>( nums[i] ); + MP4TagsSetTVEpisode( mdata, &value ); + break; + } + case OPT_TVSEASON: + { + uint32_t value = static_cast<uint32_t>( nums[i] ); + MP4TagsSetTVSeason( mdata, &value ); + break; + } + case OPT_TVNETWORK: + MP4TagsSetTVNetwork( mdata, tags[i] ); + break; + case OPT_TVEPISODEID: + MP4TagsSetTVEpisodeID( mdata, tags[i] ); + break; + case OPT_NAME: + MP4TagsSetName( mdata, tags[i] ); + break; + case OPT_TVSHOW: + MP4TagsSetTVShow( mdata, tags[i] ); + break; + case OPT_COMPOSER: + MP4TagsSetComposer( mdata, tags[i] ); + break; + case OPT_RELEASEDATE: + MP4TagsSetReleaseDate( mdata, tags[i] ); + break; + case OPT_TEMPO: + { + uint16_t value = static_cast<uint16_t>( nums[i] ); + MP4TagsSetTempo( mdata, &value ); + break; + } + case OPT_ALBUM_ARTIST: + MP4TagsSetAlbumArtist( mdata, tags[i] ); + break; + case OPT_PICTURE: + { + File in( tags[i], File::MODE_READ ); + if( !in.open() ) { + MP4TagArtwork art; + art.size = (uint32_t)in.size; + art.data = malloc( art.size ); + art.type = MP4_ART_UNDEFINED; + + File::Size nin; + if( in.read( (void*)art.data, art.size, nin ) && nin == art.size ) { + if( mdata->artworkCount ) + MP4TagsRemoveArtwork( mdata, 0 ); + MP4TagsAddArtwork( mdata, &art ); + } + + free( (void*)art.data ); + in.close(); + } + else { + fprintf( stderr, "Art file %s not found\n", tags[i] ); + } + } + } + } + } + /* Write out all tag modifications, free and close */ + MP4TagsStore( mdata, h ); + MP4TagsFree( mdata ); + MP4Close( h ); + } /* end while optind < argc */ + return 0; +} |