1 /* id3tool.c
2  *
3  * Tool for manipulating ID3 Tags in MP3 files
4 */
5 
6 /*  id3tool:  An editor for ID3 tags.
7  *  Copyright (C) 1999-2005  Christopher Collins
8  *
9  * This program was authored principly by Christopher Collins (aka
10  * Crossfire).
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions are
14  * met:
15  *
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. The name of the author, Christopher Collins, nor any of his
22  *    aliases may be used to endorse or promote products derived
23  *    from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY CHRISTOPHER COLLINS ``AS IS'' AND ANY EXPRESS
26  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
27  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28  * DISCLAIMED.  IN NO EVENT SHALL CHRISTOPHER COLLINS BE LIABLE FOR ANY
29  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36 */
37 
38 #include "config.h"
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <ctype.h>
44 
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48 
49 #ifdef HAVE_GETOPT_LONG
50 #include <getopt.h>
51 #endif
52 
53 #include "id3.h"
54 
55 struct option id3options[] = {
56 	{"set-title", required_argument, NULL, 't'},
57 	{"set-album", required_argument, NULL, 'a'},
58 	{"set-artist", required_argument, NULL, 'r'},
59 	{"set-year", required_argument, NULL, 'y'},
60 	{"set-note", required_argument, NULL, 'n'},
61 	{"set-genre", required_argument, NULL, 'g'},
62 	{"set-genre-word", required_argument, NULL, 'G'},
63 	{"set-track", required_argument, NULL, 'c'},
64 	{"genre-list", no_argument, NULL, 'l'},
65 	{"version", no_argument, NULL, 'v'},
66 	{"help", no_argument, NULL, 'h'},
67 	{0, 0, 0, 0}
68 };
69 #define ID3TOOL_GETOPT_ARGS "t:a:r:y:n:g:G:c:lvh"
70 
71 #ifdef NEED_STRDUP
72 extern char *
my_strdup(char * strin)73 my_strdup (char *strin)
74 {
75 	int	len;
76 	char	*newloc;
77 	len = strlen (strin) + 1;
78 	newloc = malloc (len);
79 	if (newloc != NULL) {
80 		strcpy (newloc, strin);
81 	}
82 	return (newloc);
83 }
84 #endif
85 
86 void
showblurb(void)87 showblurb(void)
88 {
89 	printf ("id3tool version %s\n", VERSION);
90 	printf ("Copyright (C) 1999-2005, Christopher Collins\n");
91 	printf ("id3tool comes with ABSOLUTELY NO WARRANTY.  This is free "
92 		"software, and you are\n");
93 	printf ("welcome to redistribute it under certain conditions.  For "
94 		"details refer to the\n");
95 	printf ("file \"COPYING\" included with this program.\n");
96 
97 #ifndef HAVE_GETOPT_LONG
98 	/* only display if we're using BSD getopt */
99 	printf ("This product includes software developed by the NetBSD "
100 		"Foundation, Inc. and\n");
101 	printf ("its contributors.\n");
102 #endif
103 }
104 
105 void
showusage(int argc,char ** argv)106 showusage(int argc, char **argv)
107 {
108 	printf ("usage:\n");
109 	printf ("%s [<options>] <filename>\n", argv[0]);
110 	printf ("  -t, --set-title=WORD\t\tSets the title to WORD\n");
111 	printf ("  -a, --set-album=WORD\t\tSets the album to WORD\n");
112 	printf ("  -r, --set-artist=WORD\t\tSets the artist to WORD\n");
113 	printf ("  -y, --set-year=YEAR\t\tSets the year to YEAR [4 digits]\n");
114 	printf ("  -n, --set-note=WORD\t\tSets the note to WORD\n");
115 	printf ("  -g, --set-genre=INT\t\tSets the genre code to INT\n");
116 	printf ("  -G, --set-genre-word=WORD\tSets the genre to WORD\n");
117 	printf ("  -c, --set-track=INT\t\tSets the track number to INT\n");
118 	printf ("  -l, --genre-list\t\tShows the Genre's and their codes\n");
119 	printf ("  -v, --version\t\t\tDisplays the version\n");
120 	printf ("  -h, --help\t\t\tDisplays this message\n");
121 	printf ("\nReport bugs to Chris Collins <ccollins@pcug.org.au>\n");
122 }
123 
124 int
main(int argc,char ** argv)125 main (int argc, char **argv)
126 {
127 	int		retval;
128 	char *		newtitle = NULL;
129 	char *		newalbum = NULL;
130 	char *		newartist = NULL;
131 	char *		newnote = NULL;
132 	long		newyear = -1;
133 	long		newgenre = -1;
134 	long		newtrack = -1;
135 	id3tag_t	mytag;
136 	FILE		*fptr = NULL;
137 	int		newtag = 0;
138 	char		strbuf[31];
139 	int		opt_index = 0;
140 	int		ctr;
141 
142 	retval = getopt_long (argc, argv, ID3TOOL_GETOPT_ARGS,
143 			      id3options, &opt_index);
144 	while (retval != EOF) {
145 		switch (retval) {
146 		case 't': /* set title */
147 			if (NULL != newtitle) {
148 				fprintf (stderr, "%s: title already set\n",
149 					 argv[0]);
150 				exit (1);
151 			}
152 			newtitle = strdup(optarg);
153 			break;
154 		case 'a': /* set album */
155 			if (NULL != newalbum) {
156 				fprintf (stderr, "%s: album already set\n",
157 					 argv[0]);
158 				exit (1);
159 			}
160 			newalbum = strdup(optarg);
161 			break;
162 		case 'r': /* set artist */
163 			if (NULL != newartist) {
164 				fprintf (stderr, "%s: artist already set\n",
165 					 argv[0]);
166 				exit (1);
167 			}
168 			newartist = strdup(optarg);
169 			break;
170 		case 'y': /* set year */
171 			if (newyear >= 0) {
172 				fprintf (stderr, "%s: year already set\n",
173 					 argv[0]);
174 				exit (1);
175 			}
176 			newyear = atoi(optarg);
177 			break;
178 		case 'n': /* set note */
179 			if (NULL != newnote) {
180 				fprintf (stderr, "%s: note already set\n",
181 					 argv[0]);
182 				exit (1);
183 			}
184 			newnote = strdup(optarg);
185 			break;
186 		case 'g': /* set genre */
187 			if (newgenre >= 0) {
188 				fprintf (stderr, "%s: genre already set\n",
189 					 argv[0]);
190 				exit (1);
191 			}
192 			newgenre = atoi(optarg);
193 			break;
194 		case 'c': /* set track */
195 			if (newtrack >= 0) {
196 				fprintf (stderr, "%s: track already set\n",
197 					 argv[0]);
198 				exit (1);
199 			}
200 			newtrack = atoi(optarg);
201 			break;
202 		case 'G': /* try to set a genre by name */
203 			for (ctr = 0; id3_styles[ctr].name != NULL; ctr++) {
204 				if (!strcasecmp(optarg,
205 						id3_styles[ctr].name)) {
206 					/* we found it! */
207 					break;
208 				}
209 			}
210 			if (id3_styles[ctr].name == NULL) {
211 				fprintf (stderr,
212 					 "%s: Couldn't find genre \"%s\"\n",
213 					 argv[0], optarg);
214 				exit (1);
215 			}
216 			newgenre = id3_styles[ctr].styleid;
217 			break;
218 		case 'l': /* show a list of the genres */
219 			printf ("%-40s | %s\n", "Style", "ID");
220 			printf ("------------------------------"
221 				"-----------+-----\n");
222 			for (ctr = 0; id3_styles[ctr].name != NULL; ctr++) {
223 				printf ("%-40s | 0x%02X\n",
224 					id3_styles[ctr].name,
225 					id3_styles[ctr].styleid);
226 			}
227 			exit (0);
228 		case 'v': /* get version */
229 			showblurb();
230 
231 			exit (0);
232 		case 'h': /* show usage */
233 			showblurb();
234 			printf("\n");
235 			showusage(argc, argv);
236 			exit (0);
237 		case '?':
238 			exit (1);
239 		default:
240 			break;
241 		}
242 		retval = getopt_long (argc, argv, ID3TOOL_GETOPT_ARGS,
243 				      id3options, &opt_index);
244 	}
245 	if (optind + 1 > argc) {
246 		fprintf (stderr, "%s: Not enough arguments\n", argv[0]);
247 		showblurb();
248 		printf("\n");
249 		showusage(argc, argv);
250 		exit (1);
251 	}
252 	for (ctr = optind; ctr < argc; ctr++) {
253 		fptr = fopen(argv[ctr], "rb");
254 		if (NULL == fptr) {
255 			fprintf (stderr, "%s: Can't open file \"%s\" "
256 				 "for read.\n", argv[0], argv[ctr]);
257 			exit (1);
258 		}
259 		if (id3_readtag(fptr, &mytag)) {
260 			newtag = 1;
261 			id3_cleartag(&mytag);
262 		} else {
263 			newtag = 0;
264 		}
265 		fclose(fptr);
266 		if (!newtitle && !newartist && !newalbum && !newnote &&
267 		    (newyear < 0) && (newgenre < 0) && (newtrack < 0)) {
268 			/* print id3 tag */
269 			printf ("Filename: %s\n", argv[ctr]);
270 			if (newtag) {
271 				printf ("No ID3 Tag\n");
272 			} else {
273 				if (mytag.songname[0] != '\0') {
274 					strncpy(strbuf, mytag.songname, 30);
275 					strbuf[30] = '\0';
276 					printf ("Song Title:\t%-30s\n", strbuf);
277 				}
278 				if (mytag.artist[0] != '\0') {
279 					strncpy(strbuf, mytag.artist, 30);
280 					strbuf[30] = '\0';
281 					printf ("Artist:\t\t%-30s\n", strbuf);
282 				}
283 				if (mytag.album[0] != '\0') {
284 					strncpy(strbuf, mytag.album, 30);
285 					strbuf[30] = '\0';
286 					printf ("Album:\t\t%-30s\n", strbuf);
287 				}
288 				if (mytag.note.v11.marker == '\0') {
289 					/* use v1.1 symantics */
290 					if (mytag.note.v11.note[0] != '\0') {
291 						strncpy(strbuf, mytag.note.v11.note, 28);
292 						strbuf[28] = '\0';
293 						printf ("Note:\t\t%-28s\n", strbuf);
294 					}
295 					if (mytag.note.v11.track != 0) {
296 						printf ("Track:\t\t%d\n", mytag.note.v11.track);
297 					}
298 				} else {
299 					if (mytag.note.v10.note[0] != '\0') {
300 						strncpy(strbuf, mytag.note.v10.note, 30);
301 						strbuf[30] = '\0';
302 						printf ("Note:\t\t%-30s\n", strbuf);
303 					}
304 				}
305 				if (mytag.year[0] != '\0') {
306 					strncpy(strbuf, mytag.year, 4);
307 					strbuf[4] = '\0';
308 					printf ("Year:\t\t%-4s\n", strbuf);
309 				}
310 				if (mytag.style != 0xFF) {
311 					printf ("Genre:\t\t%s (0x%X)\n",
312 						id3_findstyle(mytag.style),
313 						mytag.style);
314 				}
315 			}
316 			printf ("\n");
317 		} else {
318 			/* we have something to set! :) */
319 			if (newtitle) {
320 				memset (mytag.songname, 0, 30);
321 				strncpy (mytag.songname, newtitle, 30);
322 			}
323 			if (newartist) {
324 				memset (mytag.artist, 0, 30);
325 				strncpy (mytag.artist, newartist, 30);
326 			}
327 			if (newalbum) {
328 				memset (mytag.album, 0, 30);
329 				strncpy (mytag.album, newalbum, 30);
330 			}
331 			if (newnote) {
332 				/* check for existing v11 data */
333 				if (mytag.note.v11.marker == '\0'
334 				    && mytag.note.v11.track != 0
335 				    && (newtrack != 0)) {
336 					memset (mytag.note.v10.note, 0, 28);
337 					strncpy (mytag.note.v11.note, newnote, 28);
338 				} else {
339 					memset (mytag.note.v10.note, 0, 30);
340 					strncpy (mytag.note.v10.note, newnote, 30);
341 				}
342 			}
343 			/* basic epoch selection */
344 			if (newyear >= 0) {
345 				if (newyear < 100) {
346 					if (newyear > 70) {
347 						newyear += 1900;
348 					} else {
349 						newyear += 2000;
350 					}
351 				}
352 				sprintf (strbuf, "%4.4ld", newyear);
353 				memset (mytag.year, 0, 4);
354 				strncpy (mytag.year, strbuf, 4);
355 			}
356 			if (newgenre >= 0) {
357 				mytag.style = newgenre;
358 			}
359 			if (newtrack > 0) {
360 				mytag.note.v11.marker = '\0';
361 				mytag.note.v11.track = newtrack;
362 			}
363 
364 			fptr = fopen(argv[ctr], "r+b");
365 			if (NULL == fptr) {
366 				fprintf (stderr, "%s: Can't open file \"%s\" "
367 					 "for write.\n",  argv[0], argv[ctr]);
368 				exit (1);
369 			}
370 			if (newtag) {
371 				id3_appendtag(fptr, &mytag);
372 			} else {
373 				id3_replacetag(fptr, &mytag);
374 			}
375 			fclose(fptr);
376 		}
377 	}
378 
379 	/* we have something to set! :) */
380 	if (newtitle) {
381 		free(newtitle);
382 	}
383 	if (newartist) {
384 		free(newartist);
385 	}
386 	if (newalbum) {
387 		free (newalbum);
388 	}
389 	if (newnote) {
390 		free (newnote);
391 	}
392 	return (0);
393 }
394