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