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