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