vcomment.c 10.6 KB
Newer Older
Ralph Giles's avatar
   
Ralph Giles committed
1
2
/* This program is licensed under the GNU General Public License, 
 * version 2, a copy of which is included with this program.
Michael Smith's avatar
   
Michael Smith committed
3
4
 *
 * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au>
Ralph Giles's avatar
   
Ralph Giles committed
5
 * (c) 2001 Ralph Giles <giles@ashlu.bc.ca>
Michael Smith's avatar
   
Michael Smith committed
6
 *
Ralph Giles's avatar
   
Ralph Giles committed
7
 * Front end to show how to use vcedit;
Michael Smith's avatar
   
Michael Smith committed
8
 * Of limited usability on its own, but could be useful.
Michael Smith's avatar
   
Michael Smith committed
9
10
 */

Ralph Giles's avatar
   
Ralph Giles committed
11

Michael Smith's avatar
   
Michael Smith committed
12
#include <stdio.h>
Ralph Giles's avatar
   
Ralph Giles committed
13
#include <string.h>
Michael Smith's avatar
   
Michael Smith committed
14
#include <stdlib.h>
15
#include "getopt.h"
16
#include "utf8.h"
Ralph Giles's avatar
   
Ralph Giles committed
17

Michael Smith's avatar
   
Michael Smith committed
18
19
#include "vcedit.h"

Ralph Giles's avatar
   
Ralph Giles committed
20
21
22
23
24
25
26
/* getopt format struct */
struct option long_options[] = {
	{"list",0,0,'l'},
	{"write",0,0,'w'},
	{"help",0,0,'h'},
	{"quiet",0,0,'q'},
	{"commentfile",1,0,'c'},
27
    {"encoding", 1,0,'e'},
Ralph Giles's avatar
   
Ralph Giles committed
28
29
30
31
32
33
34
35
36
	{NULL,0,0,0}
};

/* local parameter storage from parsed options */
typedef struct {
	int	mode;
	char	*infilename, *outfilename;
	char	*commentfilename;
	FILE	*in, *out, *com;
37
38
	int commentcount;
	char **comments;
39
	int tempoutfile;
40
	char *encoding;
Ralph Giles's avatar
   
Ralph Giles committed
41
42
43
44
45
} param_t;

#define MODE_NONE  0
#define MODE_LIST  1
#define MODE_WRITE 2
46
#define MODE_APPEND 3
Ralph Giles's avatar
   
Ralph Giles committed
47
48
49
50

/* prototypes */
void usage(void);
void print_comments(FILE *out, vorbis_comment *vc);
51
int  add_comment(char *line, vorbis_comment *vc, char *encoding);
Ralph Giles's avatar
   
Ralph Giles committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

param_t	*new_param(void);
void parse_options(int argc, char *argv[], param_t *param);
void open_files(param_t *p);
void close_files(param_t *p);


/**********
   main.c

   This is the main function where options are read and written
   you should be able to just read this function and see how
   to call the vcedit routines. Details of how to pack/unpack the
   vorbis_comment structure itself are in the following two routines.
   The rest of the file is ui dressing so make the program minimally
   useful as a command line utility and can generally be ignored.

***********/

Michael Smith's avatar
   
Michael Smith committed
71
72
73
int main(int argc, char **argv)
{
	vcedit_state *state;
Ralph Giles's avatar
   
Ralph Giles committed
74
75
	vorbis_comment *vc;
	param_t	*param;
76
	int i;
Michael Smith's avatar
   
Michael Smith committed
77

Ralph Giles's avatar
   
Ralph Giles committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
	/* initialize the cmdline interface */
	param = new_param();
	parse_options(argc, argv, param);

	/* take care of opening the requested files */
	/* relevent file pointers are returned in the param struct */
	open_files(param);

	/* which mode are we in? */

	if (param->mode == MODE_LIST) {
		
		state = vcedit_new_state();

		if(vcedit_open(state, param->in) < 0)
		{
94
95
			fprintf(stderr, "Failed to open file as vorbis: %s\n", 
					vcedit_error(state));
Ralph Giles's avatar
   
Ralph Giles committed
96
97
98
99
100
101
102
103
104
105
106
107
			return 1;
		}

		/* extract and display the comments */
		vc = vcedit_comments(state);
		print_comments(param->com, vc);

		/* done */
		vcedit_clear(state);

		close_files(param);
		return 0;		
Michael Smith's avatar
   
Michael Smith committed
108
109
	}

110
	if (param->mode == MODE_WRITE || param->mode == MODE_APPEND) {
Ralph Giles's avatar
   
Ralph Giles committed
111
112
113
114
115

		state = vcedit_new_state();

		if(vcedit_open(state, param->in) < 0)
		{
116
117
			fprintf(stderr, "Failed to open file as vorbis: %s\n", 
					vcedit_error(state));
Ralph Giles's avatar
   
Ralph Giles committed
118
119
120
121
122
			return 1;
		}

		/* grab and clear the exisiting comments */
		vc = vcedit_comments(state);
123
124
125
126
127
		if(param->mode != MODE_APPEND) 
		{
			vorbis_comment_clear(vc);
			vorbis_comment_init(vc);
		}
Ralph Giles's avatar
   
Ralph Giles committed
128

129
130
		for(i=0; i < param->commentcount; i++)
		{
131
			if(add_comment(param->comments[i], vc, param->encoding) < 0)
132
133
134
				fprintf(stderr, "Bad comment: \"%s\"\n", param->comments[i]);
		}

Ralph Giles's avatar
   
Ralph Giles committed
135
		/* build the replacement structure */
136
		if(param->commentcount==0)
Ralph Giles's avatar
   
Ralph Giles committed
137
		{
Ralph Giles's avatar
   
Ralph Giles committed
138
			/* FIXME should use a resizeable buffer! */
Ralph Giles's avatar
   
Ralph Giles committed
139
140
141
			char *buf = (char *)malloc(sizeof(char)*1024);

			while (fgets(buf, 1024, param->com))
142
				if (add_comment(buf, vc, param->encoding) < 0) {
Ralph Giles's avatar
   
Ralph Giles committed
143
144
145
146
147
					fprintf(stderr,
						"bad comment: \"%s\"\n",
						buf);
				}
			
Ralph Giles's avatar
   
Ralph Giles committed
148
149
150
151
152
153
			free(buf);
		}

		/* write out the modified stream */
		if(vcedit_write(state, param->out) < 0)
		{
154
155
			fprintf(stderr, "Failed to write comments to output file: %s\n", 
					vcedit_error(state));
Ralph Giles's avatar
   
Ralph Giles committed
156
157
158
159
160
161
162
163
			return 1;
		}

		/* done */
		vcedit_clear(state);
		
		close_files(param);
		return 0;
Michael Smith's avatar
   
Michael Smith committed
164
165
	}

Ralph Giles's avatar
   
Ralph Giles committed
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
	/* should never reach this point */
	fprintf(stderr, "no action specified\n");
	return 1;
}

/**********

   Print out the comments from the vorbis structure

   this version just dumps the raw strings
   a more elegant version would use vorbis_comment_query()

***********/

void print_comments(FILE *out, vorbis_comment *vc)
{
	int i;

	for (i = 0; i < vc->comments; i++)
		fprintf(out, "%s\n", vc->user_comments[i]);
}

/**********

190
191
192
   Take a line of the form "TAG=value string", parse it, convert the
   value to UTF-8 from the specified encoding, and add it to the
   vorbis_comment structure. Error checking is performed.
Ralph Giles's avatar
   
Ralph Giles committed
193
194
195
196
197
198

   Note that this assumes a null-terminated string, which may cause
   problems with > 8-bit character sets!

***********/

199
int  add_comment(char *line, vorbis_comment *vc, char *encoding)
Ralph Giles's avatar
   
Ralph Giles committed
200
{
201
	char	*mark, *value, *utf8_value;
Ralph Giles's avatar
   
Ralph Giles committed
202
203

	/* strip any terminal newline */
Michael Smith's avatar
   
Michael Smith committed
204
	{
Ralph Giles's avatar
   
Ralph Giles committed
205
206
		int len = strlen(line);
		if (line[len-1] == '\n') line[len-1] = '\0';
Michael Smith's avatar
   
Michael Smith committed
207
208
	}

Ralph Giles's avatar
   
Ralph Giles committed
209
	/* validation: basically, we assume it's a tag
Michael Smith's avatar
   
Michael Smith committed
210
211
212
	 * if it has an '=' after one or more valid characters,
	 * as the comment spec requires. For the moment, we
	 * also restrict ourselves to 0-terminated values */
Michael Smith's avatar
   
Michael Smith committed
213

214
	mark = strchr(line, '=');
Ralph Giles's avatar
   
Ralph Giles committed
215
216
217
218
	if (mark == NULL) return -1;

	value = line;
	while (value < mark) {
Michael Smith's avatar
   
Michael Smith committed
219
220
		if(*value < 0x20 || *value > 0x7d || *value == 0x3d) return -1;
		value++;
Michael Smith's avatar
   
Michael Smith committed
221
222
	}

Ralph Giles's avatar
   
Ralph Giles committed
223
224
225
	/* split the line by turning the '=' in to a null */
	*mark = '\0';	
	value++;
Michael Smith's avatar
   
Michael Smith committed
226

227
228
229
230
	/* convert the value from the native charset to UTF-8 */
	if (utf8_encode(value, &utf8_value, encoding) == 0) {
		
		/* append the comment and return */
231
		vorbis_comment_add_tag(vc, line, utf8_value);
232
        free(utf8_value);
233
234
235
236
237
238
		return 0;
	} else {
		fprintf(stderr, "Couldn't convert comment to UTF8, "
			"cannot add\n");
		return -1;
	}
Ralph Giles's avatar
   
Ralph Giles committed
239
}
Michael Smith's avatar
   
Michael Smith committed
240
241


Ralph Giles's avatar
   
Ralph Giles committed
242
243
244
245
246
247
248
249
250
251
252
253
254
/*** ui-specific routines ***/

/**********

   Print out to usage summary for the cmdline interface (ui)

***********/

void usage(void)
{
	fprintf(stderr, 
		"Usage: \n"
		"  vorbiscomment [-l] file.ogg (to list the comments)\n"
255
		"  vorbiscomment -a in.ogg out.ogg (to append comments)\n"
Ralph Giles's avatar
   
Ralph Giles committed
256
257
258
259
		"  vorbiscomment -w in.ogg out.ogg (to modify comments)\n"
		"	in the write case, a new set of comments in the form\n"
		"	'TAG=value' is expected on stdin. This set will\n"
		"	completely replace the existing set.\n"
260
261
262
263
264
265
		"   Either of -a and -w can take only a single filename,\n"
		"   in which case a temporary file will be used.\n"
		"   -c can be used to take comments from a specified file\n"
		"   instead of stdin.\n"
		"   Example: vorbiscomment -a in.ogg -c comments.txt\n"
		"   will append the comments in comments.txt to in.ogg\n"
266
267
268
269
270
		"   Finally, you may specify any number of tags to add on\n"
		"   the command line using the -t option. e.g.\n"
		"   vorbiscomment -a in.ogg -t \"ARTIST=Some Guy\" -t \"TITLE=A Title\"\n"
		"   (note that when using this, reading comments from the comment\n"
		"   file or stdin is disabled)\n"
Ralph Giles's avatar
   
Ralph Giles committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
	); 
}


/**********

   allocate and initialize a the parameter struct

***********/

param_t *new_param(void)
{
	param_t *param = (param_t *)malloc(sizeof(param_t));

	/* mode */
	param->mode = MODE_LIST;

	/* filenames */
	param->infilename  = NULL;
	param->outfilename = NULL;
	param->commentfilename = "-";	/* default */

	/* file pointers */
	param->in = param->out = NULL;
	param->com = NULL;

297
298
299
300
	param->commentcount=0;
	param->comments=NULL;
	param->tempoutfile=0;

301
302
303
	/* character encoding */
	param->encoding = "ISO-8859-1";

Ralph Giles's avatar
   
Ralph Giles committed
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
	return param;
}

/**********
   parse_options()

   This function takes care of parsing the command line options
   with getopt() and fills out the param struct with the mode,
   flags, and filenames.

***********/

void parse_options(int argc, char *argv[], param_t *param)
{
	int ret;
	int option_index = 1;

321
	while ((ret = getopt_long(argc, argv, "ae:lwhqc:t:",
Ralph Giles's avatar
   
Ralph Giles committed
322
323
324
325
326
327
328
329
330
331
332
333
			long_options, &option_index)) != -1) {
		switch (ret) {
			case 0:
				fprintf(stderr, "Internal error parsing command options\n");
				exit(1);
				break;
			case 'l':
				param->mode = MODE_LIST;
				break;
			case 'w':
				param->mode = MODE_WRITE;
				break;
334
335
336
			case 'a':
				param->mode = MODE_APPEND;
				break;
337
			case 'e':
338
				param->encoding = strdup(optarg);
339
				break;
Ralph Giles's avatar
   
Ralph Giles committed
340
341
342
343
344
345
346
347
348
349
			case 'h':
				usage();
				exit(0);
				break;
			case 'q':
				/* set quiet flag */
				break;
			case 'c':
				param->commentfilename = strdup(optarg);
				break;
350
351
352
353
354
			case 't':
				param->comments = realloc(param->comments, 
						(param->commentcount+1)*sizeof(char *));
				param->comments[param->commentcount++] = strdup(optarg);
				break;
Ralph Giles's avatar
   
Ralph Giles committed
355
356
357
358
			default:
				usage();
				exit(1);
		}
Michael Smith's avatar
   
Michael Smith committed
359
360
	}

Ralph Giles's avatar
   
Ralph Giles committed
361
	/* remaining bits must be the filenames */
362
363
364
	if((param->mode == MODE_LIST && (argc-optind) != 1) ||
	   ((param->mode == MODE_WRITE || param->mode == MODE_APPEND) &&
	   ((argc-optind) < 1 || (argc-optind) > 2))) {
Ralph Giles's avatar
   
Ralph Giles committed
365
366
367
			usage();
			exit(1);
	}
368

Ralph Giles's avatar
   
Ralph Giles committed
369
	param->infilename = strdup(argv[optind]);
370
371
372
373
374
375
376
377
378
379
380
381
	if (param->mode == MODE_WRITE || param->mode == MODE_APPEND)
	{
		if(argc-optind == 1)
		{
			param->tempoutfile = 1;
			param->outfilename = malloc(strlen(param->infilename)+8);
			strcpy(param->outfilename, param->infilename);
			strcat(param->outfilename, ".vctemp");
		}
		else
			param->outfilename = strdup(argv[optind+1]);
	}
Ralph Giles's avatar
   
Ralph Giles committed
382
}
Michael Smith's avatar
   
Michael Smith committed
383

Ralph Giles's avatar
   
Ralph Giles committed
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
/**********
   open_files()

   This function takes care of opening the appropriate files
   based on the mode and filenames in the param structure.
   A filename of '-' is interpreted as stdin/out.

   The idea is just to hide the tedious checking so main()
   is easier to follow as an example.

***********/

void open_files(param_t *p)
{
	/* for all modes, open the input file */

	if (strncmp(p->infilename,"-",2) == 0) {
		p->in = stdin;
	} else {
		p->in = fopen(p->infilename, "rb");
	}
	if (p->in == NULL) {
		fprintf(stderr,
			"Error opening input file '%s'.\n",
			p->infilename);
		exit(1);
	}

412
	if (p->mode == MODE_WRITE || p->mode == MODE_APPEND) { 
Ralph Giles's avatar
   
Ralph Giles committed
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461

		/* open output for write mode */

		if (strncmp(p->outfilename,"-",2) == 0) {
			p->out = stdout;
		} else {
			p->out = fopen(p->outfilename, "wb");
		}
		if(p->out == NULL) {
			fprintf(stderr,
				"Error opening output file '%s'.\n",
				p->outfilename);
			exit(1);
		}

		/* commentfile is input */
		
		if ((p->commentfilename == NULL) ||
				(strncmp(p->commentfilename,"-",2) == 0)) {
			p->com = stdin;
		} else {
			p->com = fopen(p->commentfilename, "rb");
		}
		if (p->com == NULL) {
			fprintf(stderr,
				"Error opening comment file '%s'.\n",
				p->commentfilename);
			exit(1);
		}

	} else {

		/* in list mode, commentfile is output */

		if ((p->commentfilename == NULL) ||
				(strncmp(p->commentfilename,"-",2) == 0)) {
			p->com = stdout;
		} else {
			p->com = fopen(p->commentfilename, "wb");
		}
		if (p->com == NULL) {
			fprintf(stderr,
				"Error opening comment file '%s'\n",
				p->commentfilename);
			exit(1);
		}
	}

	/* all done */
Michael Smith's avatar
   
Michael Smith committed
462
463
}

Ralph Giles's avatar
   
Ralph Giles committed
464
465
466
467
468
469
470
471
472
473
474
475
476
477
/**********
   close_files()

   Do some quick clean-up.

***********/

void close_files(param_t *p)
{
	/* FIXME: should handle stdin/out */

	if (p->in != NULL) fclose(p->in);
	if (p->out != NULL) fclose(p->out);
	if (p->com != NULL) fclose(p->com);
478
479
480

	if(p->tempoutfile)
		rename(p->outfilename, p->infilename);
Ralph Giles's avatar
   
Ralph Giles committed
481
}