Use sizeof instead of magic number 4.
[flac.git] / src / metaflac / options.c
1 /* metaflac - Command-line FLAC metadata editor
2  * Copyright (C) 2001,2002,2003,2004,2005,2006,2007,2008,2009  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 */
38 struct 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
101 static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options);
102 static void append_new_operation(CommandLineOptions *options, Operation operation);
103 static void append_new_argument(CommandLineOptions *options, Argument argument);
104 static Operation *append_major_operation(CommandLineOptions *options, OperationType type);
105 static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type);
106 static Argument *find_argument(CommandLineOptions *options, ArgumentType type);
107 static Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type);
108 static Argument *append_argument(CommandLineOptions *options, ArgumentType type);
109 static FLAC__bool parse_md5(const char *src, FLAC__byte dest[16]);
110 static FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest);
111 static FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest);
112 static FLAC__bool parse_string(const char *src, char **dest);
113 static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation);
114 static FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation);
115 static FLAC__bool parse_add_padding(const char *in, unsigned *out);
116 static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out);
117 static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out);
118 static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out);
119 static FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out);
120 static void undocumented_warning(const char *opt);
121
122
123 void 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
155 FLAC__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 = 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
255 void 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
346 FLAC__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
712 void 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 = 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 > UINT32_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 = 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
733 void 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 = 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 > UINT32_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 = 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
754 Operation *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
764 Operation *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
774 Argument *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
783 Operation *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
792 Argument *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
801 FLAC__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
834 FLAC__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 */
845 static 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
861 FLAC__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
874 FLAC__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
882 FLAC__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
905 FLAC__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
952 FLAC__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
960 FLAC__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 = 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
1000 FLAC__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 = 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) == sizeof (out->entries[entry].application_id)) {
1044                                         memcpy(out->entries[entry].application_id, r, sizeof (out->entries[entry].application_id));
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
1084 FLAC__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
1095 FLAC__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
1106 void 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 }