1/* metaflac - Command-line FLAC metadata editor
2 * Copyright (C) 2001,2002,2003,2004,2005,2006,2007  Josh Coalson
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 */
18
19#if HAVE_CONFIG_H
20#  include <config.h>
21#endif
22
23#include "options.h"
24#include "usage.h"
25#include "utils.h"
26#include "FLAC/assert.h"
27#include "share/alloc.h"
28#include "share/grabbag/replaygain.h"
29#include <ctype.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33
34/*
35   share__getopt format struct; note we don't use short options so we just
36   set the 'val' field to 0 everywhere to indicate a valid option.
37*/
38struct share__option long_options_[] = {
39	/* global options */
40	{ "preserve-modtime", 0, 0, 0 },
41	{ "with-filename", 0, 0, 0 },
42	{ "no-filename", 0, 0, 0 },
43	{ "no-utf8-convert", 0, 0, 0 },
44	{ "dont-use-padding", 0, 0, 0 },
45	{ "no-cued-seekpoints", 0, 0, 0 },
46	/* shorthand operations */
47	{ "show-md5sum", 0, 0, 0 },
48	{ "show-min-blocksize", 0, 0, 0 },
49	{ "show-max-blocksize", 0, 0, 0 },
50	{ "show-min-framesize", 0, 0, 0 },
51	{ "show-max-framesize", 0, 0, 0 },
52	{ "show-sample-rate", 0, 0, 0 },
53	{ "show-channels", 0, 0, 0 },
54	{ "show-bps", 0, 0, 0 },
55	{ "show-total-samples", 0, 0, 0 },
56	{ "set-md5sum", 1, 0, 0 }, /* undocumented */
57	{ "set-min-blocksize", 1, 0, 0 }, /* undocumented */
58	{ "set-max-blocksize", 1, 0, 0 }, /* undocumented */
59	{ "set-min-framesize", 1, 0, 0 }, /* undocumented */
60	{ "set-max-framesize", 1, 0, 0 }, /* undocumented */
61	{ "set-sample-rate", 1, 0, 0 }, /* undocumented */
62	{ "set-channels", 1, 0, 0 }, /* undocumented */
63	{ "set-bps", 1, 0, 0 }, /* undocumented */
64	{ "set-total-samples", 1, 0, 0 }, /* undocumented */ /* WATCHOUT: used by test/test_flac.sh on windows */
65	{ "show-vendor-tag", 0, 0, 0 },
66	{ "show-tag", 1, 0, 0 },
67	{ "remove-all-tags", 0, 0, 0 },
68	{ "remove-tag", 1, 0, 0 },
69	{ "remove-first-tag", 1, 0, 0 },
70	{ "set-tag", 1, 0, 0 },
71	{ "set-tag-from-file", 1, 0, 0 },
72	{ "import-tags-from", 1, 0, 0 },
73	{ "export-tags-to", 1, 0, 0 },
74	{ "import-cuesheet-from", 1, 0, 0 },
75	{ "export-cuesheet-to", 1, 0, 0 },
76	{ "import-picture-from", 1, 0, 0 },
77	{ "export-picture-to", 1, 0, 0 },
78	{ "add-seekpoint", 1, 0, 0 },
79	{ "add-replay-gain", 0, 0, 0 },
80	{ "remove-replay-gain", 0, 0, 0 },
81	{ "add-padding", 1, 0, 0 },
82	/* major operations */
83	{ "help", 0, 0, 0 },
84	{ "version", 0, 0, 0 },
85	{ "list", 0, 0, 0 },
86	{ "append", 0, 0, 0 },
87	{ "remove", 0, 0, 0 },
88	{ "remove-all", 0, 0, 0 },
89	{ "merge-padding", 0, 0, 0 },
90	{ "sort-padding", 0, 0, 0 },
91	/* major operation arguments */
92	{ "block-number", 1, 0, 0 },
93	{ "block-type", 1, 0, 0 },
94	{ "except-block-type", 1, 0, 0 },
95	{ "data-format", 1, 0, 0 },
96	{ "application-data-format", 1, 0, 0 },
97	{ "from-file", 1, 0, 0 },
98	{0, 0, 0, 0}
99};
100
101static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options);
102static void append_new_operation(CommandLineOptions *options, Operation operation);
103static void append_new_argument(CommandLineOptions *options, Argument argument);
104static Operation *append_major_operation(CommandLineOptions *options, OperationType type);
105static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type);
106static Argument *find_argument(CommandLineOptions *options, ArgumentType type);
107static Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type);
108static Argument *append_argument(CommandLineOptions *options, ArgumentType type);
109static FLAC__bool parse_md5(const char *src, FLAC__byte dest[16]);
110static FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest);
111static FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest);
112static FLAC__bool parse_string(const char *src, char **dest);
113static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation);
114static FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation);
115static FLAC__bool parse_add_padding(const char *in, unsigned *out);
116static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out);
117static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out);
118static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out);
119static FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out);
120static void undocumented_warning(const char *opt);
121
122
123void init_options(CommandLineOptions *options)
124{
125	options->preserve_modtime = false;
126
127	/* '2' is a hack to mean "use default if not forced on command line" */
128	FLAC__ASSERT(true != 2);
129	options->prefix_with_filename = 2;
130
131	options->utf8_convert = true;
132	options->use_padding = true;
133	options->cued_seekpoints = true;
134	options->show_long_help = false;
135	options->show_version = false;
136	options->application_data_format_is_hexdump = false;
137
138	options->ops.operations = 0;
139	options->ops.num_operations = 0;
140	options->ops.capacity = 0;
141
142	options->args.arguments = 0;
143	options->args.num_arguments = 0;
144	options->args.capacity = 0;
145
146	options->args.checks.num_shorthand_ops = 0;
147	options->args.checks.num_major_ops = 0;
148	options->args.checks.has_block_type = false;
149	options->args.checks.has_except_block_type = false;
150
151	options->num_files = 0;
152	options->filenames = 0;
153}
154
155FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options)
156{
157	int ret;
158	int option_index = 1;
159	FLAC__bool had_error = false;
160
161	while ((ret = share__getopt_long(argc, argv, "", long_options_, &option_index)) != -1) {
162		switch (ret) {
163			case 0:
164				had_error |= !parse_option(option_index, share__optarg, options);
165				break;
166			case '?':
167			case ':':
168				had_error = true;
169				break;
170			default:
171				FLAC__ASSERT(0);
172				break;
173		}
174	}
175
176	if(options->prefix_with_filename == 2)
177		options->prefix_with_filename = (argc - share__optind > 1);
178
179	if(share__optind >= argc && !options->show_long_help && !options->show_version) {
180		fprintf(stderr,"ERROR: you must specify at least one FLAC file;\n");
181		fprintf(stderr,"       metaflac cannot be used as a pipe\n");
182		had_error = true;
183	}
184
185	options->num_files = argc - share__optind;
186
187	if(options->num_files > 0) {
188		unsigned i = 0;
189		if(0 == (options->filenames = (char**)safe_malloc_mul_2op_(sizeof(char*), /*times*/options->num_files)))
190			die("out of memory allocating space for file names list");
191		while(share__optind < argc)
192			options->filenames[i++] = local_strdup(argv[share__optind++]);
193	}
194
195	if(options->args.checks.num_major_ops > 0) {
196		if(options->args.checks.num_major_ops > 1) {
197			fprintf(stderr, "ERROR: you may only specify one major operation at a time\n");
198			had_error = true;
199		}
200		else if(options->args.checks.num_shorthand_ops > 0) {
201			fprintf(stderr, "ERROR: you may not mix shorthand and major operations\n");
202			had_error = true;
203		}
204	}
205
206	/* check for only one FLAC file used with certain options */
207	if(options->num_files > 1) {
208		if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
209			fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--import-cuesheet-from'\n");
210			had_error = true;
211		}
212		if(0 != find_shorthand_operation(options, OP__EXPORT_CUESHEET_TO)) {
213			fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--export-cuesheet-to'\n");
214			had_error = true;
215		}
216		if(0 != find_shorthand_operation(options, OP__EXPORT_PICTURE_TO)) {
217			fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--export-picture-to'\n");
218			had_error = true;
219		}
220		if(
221			0 != find_shorthand_operation(options, OP__IMPORT_VC_FROM) &&
222			0 == strcmp(find_shorthand_operation(options, OP__IMPORT_VC_FROM)->argument.filename.value, "-")
223		) {
224			fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--import-tags-from=-'\n");
225			had_error = true;
226		}
227	}
228
229	if(options->args.checks.has_block_type && options->args.checks.has_except_block_type) {
230		fprintf(stderr, "ERROR: you may not specify both '--block-type' and '--except-block-type'\n");
231		had_error = true;
232	}
233
234	if(had_error)
235		short_usage(0);
236
237	/*
238	 * We need to create an OP__ADD_SEEKPOINT operation if there is
239	 * not one already, and --import-cuesheet-from was specified but
240	 * --no-cued-seekpoints was not:
241	 */
242	if(options->cued_seekpoints) {
243		Operation *op = find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
244		if(0 != op) {
245			Operation *op2 = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
246			if(0 == op2)
247				op2 = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
248			op->argument.import_cuesheet_from.add_seekpoint_link = &(op2->argument.add_seekpoint);
249		}
250	}
251
252	return !had_error;
253}
254
255void free_options(CommandLineOptions *options)
256{
257	unsigned i;
258	Operation *op;
259	Argument *arg;
260
261	FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0);
262	FLAC__ASSERT(0 == options->args.arguments || options->args.num_arguments > 0);
263
264	for(i = 0, op = options->ops.operations; i < options->ops.num_operations; i++, op++) {
265		switch(op->type) {
266			case OP__SHOW_VC_FIELD:
267			case OP__REMOVE_VC_FIELD:
268			case OP__REMOVE_VC_FIRSTFIELD:
269				if(0 != op->argument.vc_field_name.value)
270					free(op->argument.vc_field_name.value);
271				break;
272			case OP__SET_VC_FIELD:
273				if(0 != op->argument.vc_field.field)
274					free(op->argument.vc_field.field);
275				if(0 != op->argument.vc_field.field_name)
276					free(op->argument.vc_field.field_name);
277				if(0 != op->argument.vc_field.field_value)
278					free(op->argument.vc_field.field_value);
279				break;
280			case OP__IMPORT_VC_FROM:
281			case OP__EXPORT_VC_TO:
282			case OP__EXPORT_CUESHEET_TO:
283				if(0 != op->argument.filename.value)
284					free(op->argument.filename.value);
285				break;
286			case OP__IMPORT_CUESHEET_FROM:
287				if(0 != op->argument.import_cuesheet_from.filename)
288					free(op->argument.import_cuesheet_from.filename);
289				break;
290			case OP__IMPORT_PICTURE_FROM:
291				if(0 != op->argument.specification.value)
292					free(op->argument.specification.value);
293				break;
294			case OP__EXPORT_PICTURE_TO:
295				if(0 != op->argument.export_picture_to.filename)
296					free(op->argument.export_picture_to.filename);
297				break;
298			case OP__ADD_SEEKPOINT:
299				if(0 != op->argument.add_seekpoint.specification)
300					free(op->argument.add_seekpoint.specification);
301				break;
302			default:
303				break;
304		}
305	}
306
307	for(i = 0, arg = options->args.arguments; i < options->args.num_arguments; i++, arg++) {
308		switch(arg->type) {
309			case ARG__BLOCK_NUMBER:
310				if(0 != arg->value.block_number.entries)
311					free(arg->value.block_number.entries);
312				break;
313			case ARG__BLOCK_TYPE:
314			case ARG__EXCEPT_BLOCK_TYPE:
315				if(0 != arg->value.block_type.entries)
316					free(arg->value.block_type.entries);
317				break;
318			case ARG__FROM_FILE:
319				if(0 != arg->value.from_file.file_name)
320					free(arg->value.from_file.file_name);
321				break;
322			default:
323				break;
324		}
325	}
326
327	if(0 != options->ops.operations)
328		free(options->ops.operations);
329
330	if(0 != options->args.arguments)
331		free(options->args.arguments);
332
333	if(0 != options->filenames) {
334		for(i = 0; i < options->num_files; i++) {
335			if(0 != options->filenames[i])
336				free(options->filenames[i]);
337		}
338		free(options->filenames);
339	}
340}
341
342/*
343 * local routines
344 */
345
346FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options)
347{
348	const char *opt = long_options_[option_index].name;
349	Operation *op;
350	Argument *arg;
351	FLAC__bool ok = true;
352
353	if(0 == strcmp(opt, "preserve-modtime")) {
354		options->preserve_modtime = true;
355	}
356	else if(0 == strcmp(opt, "with-filename")) {
357		options->prefix_with_filename = true;
358	}
359	else if(0 == strcmp(opt, "no-filename")) {
360		options->prefix_with_filename = false;
361	}
362	else if(0 == strcmp(opt, "no-utf8-convert")) {
363		options->utf8_convert = false;
364	}
365	else if(0 == strcmp(opt, "dont-use-padding")) {
366		options->use_padding = false;
367	}
368	else if(0 == strcmp(opt, "no-cued-seekpoints")) {
369		options->cued_seekpoints = false;
370	}
371	else if(0 == strcmp(opt, "show-md5sum")) {
372		(void) append_shorthand_operation(options, OP__SHOW_MD5SUM);
373	}
374	else if(0 == strcmp(opt, "show-min-blocksize")) {
375		(void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE);
376	}
377	else if(0 == strcmp(opt, "show-max-blocksize")) {
378		(void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE);
379	}
380	else if(0 == strcmp(opt, "show-min-framesize")) {
381		(void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE);
382	}
383	else if(0 == strcmp(opt, "show-max-framesize")) {
384		(void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE);
385	}
386	else if(0 == strcmp(opt, "show-sample-rate")) {
387		(void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE);
388	}
389	else if(0 == strcmp(opt, "show-channels")) {
390		(void) append_shorthand_operation(options, OP__SHOW_CHANNELS);
391	}
392	else if(0 == strcmp(opt, "show-bps")) {
393		(void) append_shorthand_operation(options, OP__SHOW_BPS);
394	}
395	else if(0 == strcmp(opt, "show-total-samples")) {
396		(void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES);
397	}
398	else if(0 == strcmp(opt, "set-md5sum")) {
399		op = append_shorthand_operation(options, OP__SET_MD5SUM);
400		FLAC__ASSERT(0 != option_argument);
401		if(!parse_md5(option_argument, op->argument.streaminfo_md5.value)) {
402			fprintf(stderr, "ERROR (--%s): bad MD5 sum\n", opt);
403			ok = false;
404		}
405		else
406			undocumented_warning(opt);
407	}
408	else if(0 == strcmp(opt, "set-min-blocksize")) {
409		op = append_shorthand_operation(options, OP__SET_MIN_BLOCKSIZE);
410		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BLOCK_SIZE || op->argument.streaminfo_uint32.value > FLAC__MAX_BLOCK_SIZE) {
411			fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
412			ok = false;
413		}
414		else
415			undocumented_warning(opt);
416	}
417	else if(0 == strcmp(opt, "set-max-blocksize")) {
418		op = append_shorthand_operation(options, OP__SET_MAX_BLOCKSIZE);
419		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BLOCK_SIZE || op->argument.streaminfo_uint32.value > FLAC__MAX_BLOCK_SIZE) {
420			fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
421			ok = false;
422		}
423		else
424			undocumented_warning(opt);
425	}
426	else if(0 == strcmp(opt, "set-min-framesize")) {
427		op = append_shorthand_operation(options, OP__SET_MIN_FRAMESIZE);
428		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN)) {
429			fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN);
430			ok = false;
431		}
432		else
433			undocumented_warning(opt);
434	}
435	else if(0 == strcmp(opt, "set-max-framesize")) {
436		op = append_shorthand_operation(options, OP__SET_MAX_FRAMESIZE);
437		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN)) {
438			fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN);
439			ok = false;
440		}
441		else
442			undocumented_warning(opt);
443	}
444	else if(0 == strcmp(opt, "set-sample-rate")) {
445		op = append_shorthand_operation(options, OP__SET_SAMPLE_RATE);
446		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || !FLAC__format_sample_rate_is_valid(op->argument.streaminfo_uint32.value)) {
447			fprintf(stderr, "ERROR (--%s): invalid sample rate\n", opt);
448			ok = false;
449		}
450		else
451			undocumented_warning(opt);
452	}
453	else if(0 == strcmp(opt, "set-channels")) {
454		op = append_shorthand_operation(options, OP__SET_CHANNELS);
455		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value > FLAC__MAX_CHANNELS) {
456			fprintf(stderr, "ERROR (--%s): value must be > 0 and <= %u\n", opt, FLAC__MAX_CHANNELS);
457			ok = false;
458		}
459		else
460			undocumented_warning(opt);
461	}
462	else if(0 == strcmp(opt, "set-bps")) {
463		op = append_shorthand_operation(options, OP__SET_BPS);
464		if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BITS_PER_SAMPLE || op->argument.streaminfo_uint32.value > FLAC__MAX_BITS_PER_SAMPLE) {
465			fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BITS_PER_SAMPLE, FLAC__MAX_BITS_PER_SAMPLE);
466			ok = false;
467		}
468		else
469			undocumented_warning(opt);
470	}
471	else if(0 == strcmp(opt, "set-total-samples")) {
472		op = append_shorthand_operation(options, OP__SET_TOTAL_SAMPLES);
473		if(!parse_uint64(option_argument, &(op->argument.streaminfo_uint64.value)) || op->argument.streaminfo_uint64.value >= (((FLAC__uint64)1)<<FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN)) {
474			fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN);
475			ok = false;
476		}
477		else
478			undocumented_warning(opt);
479	}
480	else if(0 == strcmp(opt, "show-vendor-tag")) {
481		(void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR);
482	}
483	else if(0 == strcmp(opt, "show-tag")) {
484		const char *violation;
485		op = append_shorthand_operation(options, OP__SHOW_VC_FIELD);
486		FLAC__ASSERT(0 != option_argument);
487		if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
488			FLAC__ASSERT(0 != violation);
489			fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
490			ok = false;
491		}
492	}
493	else if(0 == strcmp(opt, "remove-all-tags")) {
494		(void) append_shorthand_operation(options, OP__REMOVE_VC_ALL);
495	}
496	else if(0 == strcmp(opt, "remove-tag")) {
497		const char *violation;
498		op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
499		FLAC__ASSERT(0 != option_argument);
500		if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
501			FLAC__ASSERT(0 != violation);
502			fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
503			ok = false;
504		}
505	}
506	else if(0 == strcmp(opt, "remove-first-tag")) {
507		const char *violation;
508		op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD);
509		FLAC__ASSERT(0 != option_argument);
510		if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
511			FLAC__ASSERT(0 != violation);
512			fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
513			ok = false;
514		}
515	}
516	else if(0 == strcmp(opt, "set-tag")) {
517		const char *violation;
518		op = append_shorthand_operation(options, OP__SET_VC_FIELD);
519		FLAC__ASSERT(0 != option_argument);
520		op->argument.vc_field.field_value_from_file = false;
521		if(!parse_vorbis_comment_field(option_argument, &(op->argument.vc_field.field), &(op->argument.vc_field.field_name), &(op->argument.vc_field.field_value), &(op->argument.vc_field.field_value_length), &violation)) {
522			FLAC__ASSERT(0 != violation);
523			fprintf(stderr, "ERROR (--%s): malformed vorbis comment field \"%s\",\n       %s\n", opt, option_argument, violation);
524			ok = false;
525		}
526	}
527	else if(0 == strcmp(opt, "set-tag-from-file")) {
528		const char *violation;
529		op = append_shorthand_operation(options, OP__SET_VC_FIELD);
530		FLAC__ASSERT(0 != option_argument);
531		op->argument.vc_field.field_value_from_file = true;
532		if(!parse_vorbis_comment_field(option_argument, &(op->argument.vc_field.field), &(op->argument.vc_field.field_name), &(op->argument.vc_field.field_value), &(op->argument.vc_field.field_value_length), &violation)) {
533			FLAC__ASSERT(0 != violation);
534			fprintf(stderr, "ERROR (--%s): malformed vorbis comment field \"%s\",\n       %s\n", opt, option_argument, violation);
535			ok = false;
536		}
537	}
538	else if(0 == strcmp(opt, "import-tags-from")) {
539		op = append_shorthand_operation(options, OP__IMPORT_VC_FROM);
540		FLAC__ASSERT(0 != option_argument);
541		if(!parse_string(option_argument, &(op->argument.filename.value))) {
542			fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
543			ok = false;
544		}
545	}
546	else if(0 == strcmp(opt, "export-tags-to")) {
547		op = append_shorthand_operation(options, OP__EXPORT_VC_TO);
548		FLAC__ASSERT(0 != option_argument);
549		if(!parse_string(option_argument, &(op->argument.filename.value))) {
550			fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
551			ok = false;
552		}
553	}
554	else if(0 == strcmp(opt, "import-cuesheet-from")) {
555		if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
556			fprintf(stderr, "ERROR (--%s): may be specified only once\n", opt);
557			ok = false;
558		}
559		op = append_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
560		FLAC__ASSERT(0 != option_argument);
561		if(!parse_string(option_argument, &(op->argument.import_cuesheet_from.filename))) {
562			fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
563			ok = false;
564		}
565	}
566	else if(0 == strcmp(opt, "export-cuesheet-to")) {
567		op = append_shorthand_operation(options, OP__EXPORT_CUESHEET_TO);
568		FLAC__ASSERT(0 != option_argument);
569		if(!parse_string(option_argument, &(op->argument.filename.value))) {
570			fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
571			ok = false;
572		}
573	}
574	else if(0 == strcmp(opt, "import-picture-from")) {
575		op = append_shorthand_operation(options, OP__IMPORT_PICTURE_FROM);
576		FLAC__ASSERT(0 != option_argument);
577		if(!parse_string(option_argument, &(op->argument.specification.value))) {
578			fprintf(stderr, "ERROR (--%s): missing specification\n", opt);
579			ok = false;
580		}
581	}
582	else if(0 == strcmp(opt, "export-picture-to")) {
583		const Argument *arg = find_argument(options, ARG__BLOCK_NUMBER);
584		op = append_shorthand_operation(options, OP__EXPORT_PICTURE_TO);
585		FLAC__ASSERT(0 != option_argument);
586		if(!parse_string(option_argument, &(op->argument.export_picture_to.filename))) {
587			fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
588			ok = false;
589		}
590		op->argument.export_picture_to.block_number_link = arg? &(arg->value.block_number) : 0;
591	}
592	else if(0 == strcmp(opt, "add-seekpoint")) {
593		const char *violation;
594		char *spec;
595		FLAC__ASSERT(0 != option_argument);
596		if(!parse_add_seekpoint(option_argument, &spec, &violation)) {
597			FLAC__ASSERT(0 != violation);
598			fprintf(stderr, "ERROR (--%s): malformed seekpoint specification \"%s\",\n       %s\n", opt, option_argument, violation);
599			ok = false;
600		}
601		else {
602			op = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
603			if(0 == op)
604				op = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
605			local_strcat(&(op->argument.add_seekpoint.specification), spec);
606			local_strcat(&(op->argument.add_seekpoint.specification), ";");
607			free(spec);
608		}
609	}
610	else if(0 == strcmp(opt, "add-replay-gain")) {
611		(void) append_shorthand_operation(options, OP__ADD_REPLAY_GAIN);
612	}
613	else if(0 == strcmp(opt, "remove-replay-gain")) {
614		const FLAC__byte * const tags[5] = {
615			GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS,
616			GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN,
617			GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK,
618			GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN,
619			GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK
620		};
621		size_t i;
622		for(i = 0; i < sizeof(tags)/sizeof(tags[0]); i++) {
623			op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
624			op->argument.vc_field_name.value = local_strdup((const char *)tags[i]);
625		}
626	}
627	else if(0 == strcmp(opt, "add-padding")) {
628		op = append_shorthand_operation(options, OP__ADD_PADDING);
629		FLAC__ASSERT(0 != option_argument);
630		if(!parse_add_padding(option_argument, &(op->argument.add_padding.length))) {
631			fprintf(stderr, "ERROR (--%s): illegal length \"%s\", length must be >= 0 and < 2^%u\n", opt, option_argument, FLAC__STREAM_METADATA_LENGTH_LEN);
632			ok = false;
633		}
634	}
635	else if(0 == strcmp(opt, "help")) {
636		options->show_long_help = true;
637	}
638	else if(0 == strcmp(opt, "version")) {
639		options->show_version = true;
640	}
641	else if(0 == strcmp(opt, "list")) {
642		(void) append_major_operation(options, OP__LIST);
643	}
644	else if(0 == strcmp(opt, "append")) {
645		(void) append_major_operation(options, OP__APPEND);
646	}
647	else if(0 == strcmp(opt, "remove")) {
648		(void) append_major_operation(options, OP__REMOVE);
649	}
650	else if(0 == strcmp(opt, "remove-all")) {
651		(void) append_major_operation(options, OP__REMOVE_ALL);
652	}
653	else if(0 == strcmp(opt, "merge-padding")) {
654		(void) append_major_operation(options, OP__MERGE_PADDING);
655	}
656	else if(0 == strcmp(opt, "sort-padding")) {
657		(void) append_major_operation(options, OP__SORT_PADDING);
658	}
659	else if(0 == strcmp(opt, "block-number")) {
660		arg = append_argument(options, ARG__BLOCK_NUMBER);
661		FLAC__ASSERT(0 != option_argument);
662		if(!parse_block_number(option_argument, &(arg->value.block_number))) {
663			fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument);
664			ok = false;
665		}
666	}
667	else if(0 == strcmp(opt, "block-type")) {
668		arg = append_argument(options, ARG__BLOCK_TYPE);
669		FLAC__ASSERT(0 != option_argument);
670		if(!parse_block_type(option_argument, &(arg->value.block_type))) {
671			fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
672			ok = false;
673		}
674		options->args.checks.has_block_type = true;
675	}
676	else if(0 == strcmp(opt, "except-block-type")) {
677		arg = append_argument(options, ARG__EXCEPT_BLOCK_TYPE);
678		FLAC__ASSERT(0 != option_argument);
679		if(!parse_block_type(option_argument, &(arg->value.block_type))) {
680			fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
681			ok = false;
682		}
683		options->args.checks.has_except_block_type = true;
684	}
685	else if(0 == strcmp(opt, "data-format")) {
686		arg = append_argument(options, ARG__DATA_FORMAT);
687		FLAC__ASSERT(0 != option_argument);
688		if(!parse_data_format(option_argument, &(arg->value.data_format))) {
689			fprintf(stderr, "ERROR (--%s): illegal data format \"%s\"\n", opt, option_argument);
690			ok = false;
691		}
692	}
693	else if(0 == strcmp(opt, "application-data-format")) {
694		FLAC__ASSERT(0 != option_argument);
695		if(!parse_application_data_format(option_argument, &(options->application_data_format_is_hexdump))) {
696			fprintf(stderr, "ERROR (--%s): illegal application data format \"%s\"\n", opt, option_argument);
697			ok = false;
698		}
699	}
700	else if(0 == strcmp(opt, "from-file")) {
701		arg = append_argument(options, ARG__FROM_FILE);
702		FLAC__ASSERT(0 != option_argument);
703		arg->value.from_file.file_name = local_strdup(option_argument);
704	}
705	else {
706		FLAC__ASSERT(0);
707	}
708
709	return ok;
710}
711
712void append_new_operation(CommandLineOptions *options, Operation operation)
713{
714	if(options->ops.capacity == 0) {
715		options->ops.capacity = 50;
716		if(0 == (options->ops.operations = (Operation*)malloc(sizeof(Operation) * options->ops.capacity)))
717			die("out of memory allocating space for option list");
718		memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity);
719	}
720	if(options->ops.capacity <= options->ops.num_operations) {
721		unsigned original_capacity = options->ops.capacity;
722		if(options->ops.capacity > SIZE_MAX / 2) /* overflow check */
723			die("out of memory allocating space for option list");
724		options->ops.capacity *= 2;
725		if(0 == (options->ops.operations = (Operation*)safe_realloc_mul_2op_(options->ops.operations, sizeof(Operation), /*times*/options->ops.capacity)))
726			die("out of memory allocating space for option list");
727		memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity));
728	}
729
730	options->ops.operations[options->ops.num_operations++] = operation;
731}
732
733void append_new_argument(CommandLineOptions *options, Argument argument)
734{
735	if(options->args.capacity == 0) {
736		options->args.capacity = 50;
737		if(0 == (options->args.arguments = (Argument*)malloc(sizeof(Argument) * options->args.capacity)))
738			die("out of memory allocating space for option list");
739		memset(options->args.arguments, 0, sizeof(Argument) * options->args.capacity);
740	}
741	if(options->args.capacity <= options->args.num_arguments) {
742		unsigned original_capacity = options->args.capacity;
743		if(options->args.capacity > SIZE_MAX / 2) /* overflow check */
744			die("out of memory allocating space for option list");
745		options->args.capacity *= 2;
746		if(0 == (options->args.arguments = (Argument*)safe_realloc_mul_2op_(options->args.arguments, sizeof(Argument), /*times*/options->args.capacity)))
747			die("out of memory allocating space for option list");
748		memset(options->args.arguments + original_capacity, 0, sizeof(Argument) * (options->args.capacity - original_capacity));
749	}
750
751	options->args.arguments[options->args.num_arguments++] = argument;
752}
753
754Operation *append_major_operation(CommandLineOptions *options, OperationType type)
755{
756	Operation op;
757	memset(&op, 0, sizeof(op));
758	op.type = type;
759	append_new_operation(options, op);
760	options->args.checks.num_major_ops++;
761	return options->ops.operations + (options->ops.num_operations - 1);
762}
763
764Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type)
765{
766	Operation op;
767	memset(&op, 0, sizeof(op));
768	op.type = type;
769	append_new_operation(options, op);
770	options->args.checks.num_shorthand_ops++;
771	return options->ops.operations + (options->ops.num_operations - 1);
772}
773
774Argument *find_argument(CommandLineOptions *options, ArgumentType type)
775{
776	unsigned i;
777	for(i = 0; i < options->args.num_arguments; i++)
778		if(options->args.arguments[i].type == type)
779			return &options->args.arguments[i];
780	return 0;
781}
782
783Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type)
784{
785	unsigned i;
786	for(i = 0; i < options->ops.num_operations; i++)
787		if(options->ops.operations[i].type == type)
788			return &options->ops.operations[i];
789	return 0;
790}
791
792Argument *append_argument(CommandLineOptions *options, ArgumentType type)
793{
794	Argument arg;
795	memset(&arg, 0, sizeof(arg));
796	arg.type = type;
797	append_new_argument(options, arg);
798	return options->args.arguments + (options->args.num_arguments - 1);
799}
800
801FLAC__bool parse_md5(const char *src, FLAC__byte dest[16])
802{
803	unsigned i, d;
804	int c;
805	FLAC__ASSERT(0 != src);
806	if(strlen(src) != 32)
807		return false;
808	/* strtoul() accepts negative numbers which we do not want, so we do it the hard way */
809	for(i = 0; i < 16; i++) {
810		c = (int)(*src++);
811		if(isdigit(c))
812			d = (unsigned)(c - '0');
813		else if(c >= 'a' && c <= 'f')
814			d = (unsigned)(c - 'a') + 10u;
815		else if(c >= 'A' && c <= 'F')
816			d = (unsigned)(c - 'A') + 10u;
817		else
818			return false;
819		d <<= 4;
820		c = (int)(*src++);
821		if(isdigit(c))
822			d |= (unsigned)(c - '0');
823		else if(c >= 'a' && c <= 'f')
824			d |= (unsigned)(c - 'a') + 10u;
825		else if(c >= 'A' && c <= 'F')
826			d |= (unsigned)(c - 'A') + 10u;
827		else
828			return false;
829		dest[i] = (FLAC__byte)d;
830	}
831	return true;
832}
833
834FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest)
835{
836	FLAC__ASSERT(0 != src);
837	if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
838		return false;
839	*dest = strtoul(src, 0, 10);
840	return true;
841}
842
843#ifdef _MSC_VER
844/* There's no strtoull() in MSVC6 so we just write a specialized one */
845static FLAC__uint64 local__strtoull(const char *src)
846{
847	FLAC__uint64 ret = 0;
848	int c;
849	FLAC__ASSERT(0 != src);
850	while(0 != (c = *src++)) {
851		c -= '0';
852		if(c >= 0 && c <= 9)
853			ret = (ret * 10) + c;
854		else
855			break;
856	}
857	return ret;
858}
859#endif
860
861FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest)
862{
863	FLAC__ASSERT(0 != src);
864	if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
865		return false;
866#ifdef _MSC_VER
867	*dest = local__strtoull(src);
868#else
869	*dest = strtoull(src, 0, 10);
870#endif
871	return true;
872}
873
874FLAC__bool parse_string(const char *src, char **dest)
875{
876	if(0 == src || strlen(src) == 0)
877		return false;
878	*dest = strdup(src);
879	return true;
880}
881
882FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation)
883{
884	static const char * const violations[] = {
885		"field name contains invalid character"
886	};
887
888	char *q, *s;
889
890	s = local_strdup(field_ref);
891
892	for(q = s; *q; q++) {
893		if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
894			free(s);
895			*violation = violations[0];
896			return false;
897		}
898	}
899
900	*name = s;
901
902	return true;
903}
904
905FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation)
906{
907	static const char *garbled_ = "garbled specification";
908	const unsigned n = strlen(in);
909
910	FLAC__ASSERT(0 != in);
911	FLAC__ASSERT(0 != out);
912
913	if(n == 0) {
914		*violation = "specification is empty";
915		return false;
916	}
917
918	if(n > strspn(in, "0123456789.Xsx")) {
919		*violation = "specification contains invalid character";
920		return false;
921	}
922
923	if(in[n-1] == 'X') {
924		if(n > 1) {
925			*violation = garbled_;
926			return false;
927		}
928	}
929	else if(in[n-1] == 's') {
930		if(n-1 > strspn(in, "0123456789.")) {
931			*violation = garbled_;
932			return false;
933		}
934	}
935	else if(in[n-1] == 'x') {
936		if(n-1 > strspn(in, "0123456789")) {
937			*violation = garbled_;
938			return false;
939		}
940	}
941	else {
942		if(n > strspn(in, "0123456789")) {
943			*violation = garbled_;
944			return false;
945		}
946	}
947
948	*out = local_strdup(in);
949	return true;
950}
951
952FLAC__bool parse_add_padding(const char *in, unsigned *out)
953{
954	FLAC__ASSERT(0 != in);
955	FLAC__ASSERT(0 != out);
956	*out = (unsigned)strtoul(in, 0, 10);
957	return *out < (1u << FLAC__STREAM_METADATA_LENGTH_LEN);
958}
959
960FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out)
961{
962	char *p, *q, *s, *end;
963	long i;
964	unsigned entry;
965
966	if(*in == '\0')
967		return false;
968
969	s = local_strdup(in);
970
971	/* first count the entries */
972	for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
973		;
974
975	/* make space */
976	FLAC__ASSERT(out->num_entries > 0);
977	if(0 == (out->entries = (unsigned*)safe_malloc_mul_2op_(sizeof(unsigned), /*times*/out->num_entries)))
978		die("out of memory allocating space for option list");
979
980	/* load 'em up */
981	entry = 0;
982	q = s;
983	while(q) {
984		FLAC__ASSERT(entry < out->num_entries);
985		if(0 != (p = strchr(q, ',')))
986			*p++ = '\0';
987		if(!isdigit((int)(*q)) || (i = strtol(q, &end, 10)) < 0 || *end) {
988			free(s);
989			return false;
990		}
991		out->entries[entry++] = (unsigned)i;
992		q = p;
993	}
994	FLAC__ASSERT(entry == out->num_entries);
995
996	free(s);
997	return true;
998}
999
1000FLAC__bool parse_block_type(const char *in, Argument_BlockType *out)
1001{
1002	char *p, *q, *r, *s;
1003	unsigned entry;
1004
1005	if(*in == '\0')
1006		return false;
1007
1008	s = local_strdup(in);
1009
1010	/* first count the entries */
1011	for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
1012		;
1013
1014	/* make space */
1015	FLAC__ASSERT(out->num_entries > 0);
1016	if(0 == (out->entries = (Argument_BlockTypeEntry*)safe_malloc_mul_2op_(sizeof(Argument_BlockTypeEntry), /*times*/out->num_entries)))
1017		die("out of memory allocating space for option list");
1018
1019	/* load 'em up */
1020	entry = 0;
1021	q = s;
1022	while(q) {
1023		FLAC__ASSERT(entry < out->num_entries);
1024		if(0 != (p = strchr(q, ',')))
1025			*p++ = 0;
1026		r = strchr(q, ':');
1027		if(r)
1028			*r++ = '\0';
1029		if(0 != r && 0 != strcmp(q, "APPLICATION")) {
1030			free(s);
1031			return false;
1032		}
1033		if(0 == strcmp(q, "STREAMINFO")) {
1034			out->entries[entry++].type = FLAC__METADATA_TYPE_STREAMINFO;
1035		}
1036		else if(0 == strcmp(q, "PADDING")) {
1037			out->entries[entry++].type = FLAC__METADATA_TYPE_PADDING;
1038		}
1039		else if(0 == strcmp(q, "APPLICATION")) {
1040			out->entries[entry].type = FLAC__METADATA_TYPE_APPLICATION;
1041			out->entries[entry].filter_application_by_id = (0 != r);
1042			if(0 != r) {
1043				if(strlen(r) == 4) {
1044					strcpy(out->entries[entry].application_id, r);
1045				}
1046				else if(strlen(r) == 10 && strncmp(r, "0x", 2) == 0 && strspn(r+2, "0123456789ABCDEFabcdef") == 8) {
1047					FLAC__uint32 x = strtoul(r+2, 0, 16);
1048					out->entries[entry].application_id[3] = (FLAC__byte)(x & 0xff);
1049					out->entries[entry].application_id[2] = (FLAC__byte)((x>>=8) & 0xff);
1050					out->entries[entry].application_id[1] = (FLAC__byte)((x>>=8) & 0xff);
1051					out->entries[entry].application_id[0] = (FLAC__byte)((x>>=8) & 0xff);
1052				}
1053				else {
1054					free(s);
1055					return false;
1056				}
1057			}
1058			entry++;
1059		}
1060		else if(0 == strcmp(q, "SEEKTABLE")) {
1061			out->entries[entry++].type = FLAC__METADATA_TYPE_SEEKTABLE;
1062		}
1063		else if(0 == strcmp(q, "VORBIS_COMMENT")) {
1064			out->entries[entry++].type = FLAC__METADATA_TYPE_VORBIS_COMMENT;
1065		}
1066		else if(0 == strcmp(q, "CUESHEET")) {
1067			out->entries[entry++].type = FLAC__METADATA_TYPE_CUESHEET;
1068		}
1069		else if(0 == strcmp(q, "PICTURE")) {
1070			out->entries[entry++].type = FLAC__METADATA_TYPE_PICTURE;
1071		}
1072		else {
1073			free(s);
1074			return false;
1075		}
1076		q = p;
1077	}
1078	FLAC__ASSERT(entry == out->num_entries);
1079
1080	free(s);
1081	return true;
1082}
1083
1084FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out)
1085{
1086	if(0 == strcmp(in, "binary"))
1087		out->is_binary = true;
1088	else if(0 == strcmp(in, "text"))
1089		out->is_binary = false;
1090	else
1091		return false;
1092	return true;
1093}
1094
1095FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out)
1096{
1097	if(0 == strcmp(in, "hexdump"))
1098		*out = true;
1099	else if(0 == strcmp(in, "text"))
1100		*out = false;
1101	else
1102		return false;
1103	return true;
1104}
1105
1106void undocumented_warning(const char *opt)
1107{
1108	fprintf(stderr, "WARNING: undocmented option --%s should be used with caution,\n         only for repairing a damaged STREAMINFO block\n", opt);
1109}
1110