flesh out the option processing
[platform/upstream/flac.git] / src / metaflac / main.c
1 /* metaflac - Command-line FLAC metadata editor
2  * Copyright (C) 2001,2002  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 /*@@@
20 more powerful operations yet to add:
21         add a seektable, using same args as flac
22 */
23
24 #if HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include "FLAC/assert.h"
29 #include "FLAC/metadata.h"
30 #include <ctype.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #if HAVE_GETOPT_LONG
37 #  include <getopt.h>
38 #else
39 #  include "share/getopt.h"
40 #endif
41
42 /*
43    getopt format struct; note we don't use short options so we just
44    set the 'val' field to 0 everywhere to indicate a valid option.
45 */
46 static struct option long_options_[] = {
47         /* global options */
48     { "preserve-modtime", 0, 0, 0 }, 
49     { "with-filename", 0, 0, 0 }, 
50     { "no-filename", 0, 0, 0 }, 
51     { "dont-use-padding", 0, 0, 0 }, 
52         /* shorthand operations */
53     { "show-md5sum", 0, 0, 0 }, 
54     { "show-min-blocksize", 0, 0, 0 }, 
55     { "show-max-blocksize", 0, 0, 0 }, 
56     { "show-min-framesize", 0, 0, 0 }, 
57     { "show-max-framesize", 0, 0, 0 }, 
58     { "show-sample-rate", 0, 0, 0 }, 
59     { "show-channels", 0, 0, 0 }, 
60     { "show-bps", 0, 0, 0 }, 
61     { "show-total-samples", 0, 0, 0 }, 
62     { "show-vc-vendor", 0, 0, 0 }, 
63     { "show-vc-field", 1, 0, 0 }, 
64     { "remove-vc-all", 0, 0, 0 },
65     { "remove-vc-field", 1, 0, 0 },
66     { "remove-vc-firstfield", 1, 0, 0 },
67     { "set-vc-field", 1, 0, 0 },
68         /* major operations */
69     { "help", 0, 0, 0 },
70     { "list", 0, 0, 0 },
71     { "append", 0, 0, 0 },
72     { "remove", 0, 0, 0 },
73     { "remove-all", 0, 0, 0 },
74     { "merge-padding", 0, 0, 0 },
75     { "sort-padding", 0, 0, 0 },
76     { "block-number", 1, 0, 0 },
77     { "block-type", 1, 0, 0 },
78     { "except-block-type", 1, 0, 0 },
79     { "data-format", 1, 0, 0 },
80     { "application-data-format", 1, 0, 0 },
81     { "from-file", 1, 0, 0 },
82     {0, 0, 0, 0}
83 };
84
85 typedef enum {
86         OP__SHOW_MD5SUM,
87         OP__SHOW_MIN_BLOCKSIZE,
88         OP__SHOW_MAX_BLOCKSIZE,
89         OP__SHOW_MIN_FRAMESIZE,
90         OP__SHOW_MAX_FRAMESIZE,
91         OP__SHOW_SAMPLE_RATE,
92         OP__SHOW_CHANNELS,
93         OP__SHOW_BPS,
94         OP__SHOW_TOTAL_SAMPLES,
95         OP__SHOW_VC_VENDOR,
96         OP__SHOW_VC_FIELD,
97         OP__REMOVE_VC_ALL,
98         OP__REMOVE_VC_FIELD,
99         OP__REMOVE_VC_FIRSTFIELD,
100         OP__SET_VC_FIELD,
101         OP__HELP,
102         OP__LIST,
103         OP__APPEND,
104         OP__REMOVE,
105         OP__REMOVE_ALL,
106         OP__MERGE_PADDING,
107         OP__SORT_PADDING,
108         OP__BLOCK_NUMBER,
109         OP__BLOCK_TYPE,
110         OP__EXCEPT_BLOCK_TYPE,
111         OP__DATA_FORMAT,
112         OP__APPLICATION_DATA_FORMAT,
113         OP__FROM_FILE
114 } OperationType;
115
116 typedef struct {
117         char *field_name;
118 } Argument_VcFieldName;
119
120 typedef struct {
121         char *field_name;
122         /* according to the vorbis spec, field values can contain \0 so simple C strings are not enough here */
123         unsigned field_value_length;
124         char *field_value;
125 } Argument_VcField;
126
127 typedef struct {
128         unsigned num_entries;
129         unsigned *entries;
130 } Argument_BlockNumber;
131
132 typedef struct {
133         int dummy;
134 } Argument_BlockType;
135
136 typedef struct {
137         FLAC__bool is_binary;
138 } Argument_DataFormat;
139
140 typedef struct {
141         FLAC__bool is_hexdump;
142 } Argument_ApplicationDataFormat;
143
144 typedef struct {
145         char *file_name;
146 } Argument_FromFile;
147
148 typedef struct {
149         OperationType type;
150         union {
151                 Argument_VcFieldName show_vc_field;
152                 Argument_VcFieldName remove_vc_field;
153                 Argument_VcFieldName remove_vc_firstfield;
154                 Argument_VcField set_vc_field;
155                 Argument_BlockNumber block_number;
156                 Argument_BlockType block_type;
157                 Argument_BlockType except_block_type;
158                 Argument_DataFormat data_format;
159                 Argument_ApplicationDataFormat application_data_format;
160                 Argument_FromFile from_file;
161         } argument;
162 } Operation;
163
164 typedef struct {
165         FLAC__bool preserve_modtime;
166         FLAC__bool prefix_with_filename;
167         FLAC__bool use_padding;
168         struct {
169                 unsigned num_shorthand_ops;
170                 unsigned num_major_ops;
171                 FLAC__bool has_block_type;
172                 FLAC__bool has_except_block_type;
173         } checks;
174         struct {
175                 Operation *operations;
176                 unsigned num_operations;
177                 unsigned capacity;
178         } ops;
179 } CommandLineOptions;
180
181 static void init_options(CommandLineOptions *options);
182 static FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options);
183 static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options);
184 static void free_options(CommandLineOptions *options);
185 static void append_operation(CommandLineOptions *options, Operation operation);
186 static Operation *append_major_operation(CommandLineOptions *options, OperationType type);
187 static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type);
188 static int short_usage(const char *message, ...);
189 static int long_usage(const char *message, ...);
190 static void hexdump(const FLAC__byte *buf, unsigned bytes, const char *indent);
191 static char *local_strdup(const char *source);
192 static FLAC__bool parse_vorbis_comment_field(const char *field, char **name, char **value, unsigned *length);
193 static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out);
194 static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out);
195 static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out);
196 static FLAC__bool parse_application_data_format(const char *in, Argument_ApplicationDataFormat *out);
197
198 int main(int argc, char *argv[])
199 {
200         CommandLineOptions options;
201         int ret;
202
203         init_options(&options);
204
205         ret = !parse_options(argc, argv, &options);
206
207         free_options(&options);
208
209         return ret;
210 }
211
212 void init_options(CommandLineOptions *options)
213 {
214         options->preserve_modtime = false;
215
216         /* hack to mean "use default if not forced on command line" */
217         FLAC__ASSERT(true != 2);
218         options->prefix_with_filename = 2;
219
220         options->use_padding = true;
221         options->checks.num_shorthand_ops = 0;
222         options->checks.num_major_ops = 0;
223         options->checks.has_block_type = false;
224         options->checks.has_except_block_type = false;
225
226         options->ops.operations = 0;
227         options->ops.num_operations = 0;
228         options->ops.capacity = 0;
229 }
230
231 FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options)
232 {
233     int ret;
234     int option_index = 1;
235         FLAC__bool had_error = false;
236
237     while ((ret = getopt_long(argc, argv, "", long_options_, &option_index)) != -1) {
238         switch (ret) {
239             case 0:
240                                 had_error |= !parse_option(option_index, optarg, options);
241                 break;
242                         case '?':
243                         case ':':
244                 short_usage(0);
245                 return false;
246                                 had_error = true;
247             default:
248                                 FLAC__ASSERT(0);
249         }
250     }
251
252         if(options->prefix_with_filename == 2)
253                 options->prefix_with_filename = (argc - optind > 1);
254
255         if(optind < argc) {
256                 while(optind < argc)
257                         printf("%s ", argv[optind++]);
258                 printf("\n");
259         }
260
261         return had_error;
262 }
263
264 FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options)
265 {
266         const char *opt = long_options_[option_index].name;
267         Operation *op;
268         FLAC__bool ret = true;
269
270     if(0 == strcmp(opt, "preserve-modtime")) {
271                 options->preserve_modtime = true;
272         }
273     else if(0 == strcmp(opt, "with-filename")) {
274                 options->prefix_with_filename = true;
275         }
276     else if(0 == strcmp(opt, "no-filename")) {
277                 options->prefix_with_filename = false;
278         }
279     else if(0 == strcmp(opt, "dont-use-padding")) {
280                 options->use_padding = false;
281         }
282     else if(0 == strcmp(opt, "show-md5sum")) {
283                 (void) append_shorthand_operation(options, OP__SHOW_MD5SUM);
284         }
285     else if(0 == strcmp(opt, "show-min-blocksize")) {
286                 (void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE);
287         }
288     else if(0 == strcmp(opt, "show-max-blocksize")) {
289                 (void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE);
290         }
291     else if(0 == strcmp(opt, "show-min-framesize")) {
292                 (void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE);
293         }
294     else if(0 == strcmp(opt, "show-max-framesize")) {
295                 (void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE);
296         }
297     else if(0 == strcmp(opt, "show-sample-rate")) {
298                 (void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE);
299         }
300     else if(0 == strcmp(opt, "show-channels")) {
301                 (void) append_shorthand_operation(options, OP__SHOW_CHANNELS);
302         }
303     else if(0 == strcmp(opt, "show-bps")) {
304                 (void) append_shorthand_operation(options, OP__SHOW_BPS);
305         }
306     else if(0 == strcmp(opt, "show-total-samples")) {
307                 (void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES);
308         }
309     else if(0 == strcmp(opt, "show-vc-vendor")) {
310                 (void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR);
311         }
312     else if(0 == strcmp(opt, "show-vc-field")) {
313                 op = append_shorthand_operation(options, OP__SHOW_VC_FIELD);
314                 FLAC__ASSERT(0 != option_argument);
315                 op->argument.show_vc_field.field_name = local_strdup(option_argument);
316         }
317     else if(0 == strcmp(opt, "remove-vc-all")) {
318                 (void) append_shorthand_operation(options, OP__REMOVE_VC_ALL);
319         }
320     else if(0 == strcmp(opt, "remove-vc-field")) {
321                 op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
322                 FLAC__ASSERT(0 != option_argument);
323                 op->argument.remove_vc_field.field_name = local_strdup(option_argument);
324         }
325     else if(0 == strcmp(opt, "remove-vc-firstfield")) {
326                 op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD);
327                 FLAC__ASSERT(0 != option_argument);
328                 op->argument.remove_vc_firstfield.field_name = local_strdup(option_argument);
329         }
330     else if(0 == strcmp(opt, "set-vc-field")) {
331                 op = append_shorthand_operation(options, OP__SET_VC_FIELD);
332                 FLAC__ASSERT(0 != option_argument);
333                 if(!parse_vorbis_comment_field(option_argument, &(op->argument.set_vc_field.field_name), &(op->argument.set_vc_field.field_value), &(op->argument.set_vc_field.field_value_length))) {
334                         fprintf(stderr, "ERROR: malformed vorbis comment field \"%s\"\n", option_argument);
335                         ret = false;
336                 }
337         }
338     else if(0 == strcmp(opt, "help")) {
339                 (void) append_major_operation(options, OP__HELP);
340         }
341     else if(0 == strcmp(opt, "list")) {
342                 (void) append_major_operation(options, OP__LIST);
343         }
344     else if(0 == strcmp(opt, "append")) {
345                 (void) append_major_operation(options, OP__APPEND);
346         }
347     else if(0 == strcmp(opt, "remove")) {
348                 (void) append_major_operation(options, OP__REMOVE);
349         }
350     else if(0 == strcmp(opt, "remove-all")) {
351                 (void) append_major_operation(options, OP__REMOVE_ALL);
352         }
353     else if(0 == strcmp(opt, "merge-padding")) {
354                 (void) append_major_operation(options, OP__MERGE_PADDING);
355         }
356     else if(0 == strcmp(opt, "sort-padding")) {
357                 (void) append_major_operation(options, OP__SORT_PADDING);
358         }
359     else if(0 == strcmp(opt, "block-number")) {
360                 op = append_major_operation(options, OP__BLOCK_NUMBER);
361                 FLAC__ASSERT(0 != option_argument);
362                 if(!parse_block_number(option_argument, &(op->argument.block_number))) {
363                         fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument);
364                         ret = false;
365                 }
366         }
367     else if(0 == strcmp(opt, "block-type")) {
368                 op = append_major_operation(options, OP__BLOCK_TYPE);
369                 FLAC__ASSERT(0 != option_argument);
370                 if(!parse_block_type(option_argument, &(op->argument.block_type))) {
371                         fprintf(stderr, "ERROR: malformed block type specification \"%s\"\n", option_argument);
372                         ret = false;
373                 }
374         }
375     else if(0 == strcmp(opt, "except-block-type")) {
376                 op = append_major_operation(options, OP__EXCEPT_BLOCK_TYPE);
377                 FLAC__ASSERT(0 != option_argument);
378                 if(!parse_block_type(option_argument, &(op->argument.except_block_type))) {
379                         fprintf(stderr, "ERROR: malformed block type specification \"%s\"\n", option_argument);
380                         ret = false;
381                 }
382         }
383     else if(0 == strcmp(opt, "data-format")) {
384                 op = append_major_operation(options, OP__DATA_FORMAT);
385                 FLAC__ASSERT(0 != option_argument);
386                 if(!parse_data_format(option_argument, &(op->argument.data_format))) {
387                         fprintf(stderr, "ERROR: illegal data format \"%s\"\n", option_argument);
388                         ret = false;
389                 }
390         }
391     else if(0 == strcmp(opt, "application-data-format")) {
392                 op = append_major_operation(options, OP__APPLICATION_DATA_FORMAT);
393                 FLAC__ASSERT(0 != option_argument);
394                 if(!parse_application_data_format(option_argument, &(op->argument.application_data_format))) {
395                         fprintf(stderr, "ERROR: illegal application data format \"%s\"\n", option_argument);
396                         ret = false;
397                 }
398         }
399     else if(0 == strcmp(opt, "from-file")) {
400                 op = append_major_operation(options, OP__FROM_FILE);
401                 FLAC__ASSERT(0 != option_argument);
402                 op->argument.from_file.file_name = local_strdup(option_argument);
403         }
404         else {
405                 FLAC__ASSERT(0);
406         }
407
408         return ret;
409 }
410
411 void free_options(CommandLineOptions *options)
412 {
413         unsigned i;
414         Operation *op;
415
416         FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0);
417
418         for(i = 0; i < options->ops.num_operations; i++) {
419                 op = options->ops.operations + i;
420                 switch(op->type) {
421                         case OP__SHOW_VC_FIELD:
422                         case OP__REMOVE_VC_FIELD:
423                         case OP__REMOVE_VC_FIRSTFIELD:
424                                 FLAC__ASSERT(0 != op->argument.show_vc_field.field_name);
425                                 free(op->argument.show_vc_field.field_name);
426                                 break;
427                         case OP__SET_VC_FIELD:
428                                 FLAC__ASSERT(0 != op->argument.set_vc_field.field_name);
429                                 free(op->argument.set_vc_field.field_name);
430                                 if(0 != op->argument.set_vc_field.field_value)
431                                         free(op->argument.set_vc_field.field_value);
432                                 break;
433                         case OP__BLOCK_NUMBER:
434                                 /*@@@*/
435                                 break;
436                         case OP__BLOCK_TYPE:
437                         case OP__EXCEPT_BLOCK_TYPE:
438                                 /*@@@*/
439                                 break;
440                         case OP__FROM_FILE:
441                                 FLAC__ASSERT(0 != op->argument.from_file.file_name);
442                                 free(op->argument.from_file.file_name);
443                                 break;
444                         default:
445                                 break;
446                 }
447         }
448
449         if(0 != options->ops.operations)
450                 free(options->ops.operations);
451 }
452
453 void append_operation(CommandLineOptions *options, Operation operation)
454 {
455         if(options->ops.capacity == 0) {
456                 options->ops.capacity = 50;
457                 if(0 == (options->ops.operations = malloc(sizeof(Operation) * options->ops.capacity))) {
458                         fprintf(stderr, "ERROR: out of memory allocating space for option list\n");
459                         exit(1);
460                 }
461                 memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity);
462         }
463         if(options->ops.capacity <= options->ops.num_operations) {
464                 unsigned original_capacity = options->ops.capacity;
465                 options->ops.capacity *= 4;
466                 if(0 == (options->ops.operations = realloc(options->ops.operations, sizeof(Operation) * options->ops.capacity))) {
467                         fprintf(stderr, "ERROR: out of memory allocating space for option list\n");
468                         exit(1);
469                 }
470                 memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity));
471         }
472
473         options->ops.operations[options->ops.num_operations++] = operation;
474 }
475
476 Operation *append_major_operation(CommandLineOptions *options, OperationType type)
477 {
478         Operation op;
479         op.type = type;
480         append_operation(options, op);
481         options->checks.num_major_ops++;
482         return options->ops.operations + (options->ops.num_operations - 1);
483 }
484
485 Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type)
486 {
487         Operation op;
488         op.type = type;
489         append_operation(options, op);
490         options->checks.num_shorthand_ops++;
491         return options->ops.operations + (options->ops.num_operations - 1);
492 }
493
494 static void usage_header(FILE *out)
495 {
496         fprintf(out, "==============================================================================\n");
497         fprintf(out, "metaflac - Command-line FLAC metadata editor version %s\n", FLAC__VERSION_STRING);
498         fprintf(out, "Copyright (C) 2001,2002  Josh Coalson\n");
499         fprintf(out, "\n");
500         fprintf(out, "This program is free software; you can redistribute it and/or\n");
501         fprintf(out, "modify it under the terms of the GNU General Public License\n");
502         fprintf(out, "as published by the Free Software Foundation; either version 2\n");
503         fprintf(out, "of the License, or (at your option) any later version.\n");
504         fprintf(out, "\n");
505         fprintf(out, "This program is distributed in the hope that it will be useful,\n");
506         fprintf(out, "but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
507         fprintf(out, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
508         fprintf(out, "GNU General Public License for more details.\n");
509         fprintf(out, "\n");
510         fprintf(out, "You should have received a copy of the GNU General Public License\n");
511         fprintf(out, "along with this program; if not, write to the Free Software\n");
512         fprintf(out, "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n");
513         fprintf(out, "==============================================================================\n");
514 }
515
516 static void usage_summary(FILE *out)
517 {
518     fprintf(out, "Usage:\n");
519     fprintf(out, "  metaflac [options] [operations] FLACfile [FLACfile ...]\n");
520     fprintf(out, "\n");
521     fprintf(out, "Use metaflac to list, add, remove, or edit metadata in one or more FLAC files.\n");
522     fprintf(out, "You may perform one major operation, or many shorthand operations at a time.\n");
523     fprintf(out, "\n");
524     fprintf(out, "Options:\n");
525     fprintf(out, "--preserve-modtime    Preserve the original modification time in spite of edits\n");
526     fprintf(out, "--with-filename       Prefix each output line with the FLAC file name\n");
527     fprintf(out, "                      (the default if more than one FLAC file is specified)\n");
528     fprintf(out, "--no-filename         Do not prefix each output line with the FLAC file name\n");
529     fprintf(out, "                      (the default if only one FLAC file is specified)\n");
530     fprintf(out, "--dont-use-padding    By default metaflac tries to use padding where possible\n");
531     fprintf(out, "                      to avoid rewriting the entire file if the metadata size\n");
532     fprintf(out, "                      changes.  Use this option to tell metaflac to not use\n");
533     fprintf(out, "                      padding at all.\n");
534 }
535
536 int short_usage(const char *message, ...)
537 {
538         va_list args;
539
540         if(message) {
541                 va_start(args, message);
542
543                 (void) vfprintf(stderr, message, args);
544
545                 va_end(args);
546
547         }
548         usage_header(stderr);
549         fprintf(stderr, "\n");
550         fprintf(stderr, "This is the short help; for full help use 'metaflac --help'\n");
551         fprintf(stderr, "\n");
552         usage_summary(stderr);
553
554         return message? 1 : 0;
555 }
556
557 int long_usage(const char *message, ...)
558 {
559         FILE *out = (message? stderr : stdout);
560         va_list args;
561
562         if(message) {
563                 va_start(args, message);
564
565                 (void) vfprintf(stderr, message, args);
566
567                 va_end(args);
568
569         }
570         usage_header(out);
571     fprintf(out, "\n");
572         usage_summary(out);
573     fprintf(out, "\n");
574     fprintf(out, "Shorthand operations:\n");
575     fprintf(out, "--show-md5sum         Show the MD5 signature from the STREAMINFO block.\n");
576     fprintf(out, "--show-min-blocksize  Show the minimum block size from the STREAMINFO block.\n");
577     fprintf(out, "--show-max-blocksize  Show the maximum block size from the STREAMINFO block.\n");
578     fprintf(out, "--show-min-framesize  Show the minimum frame size from the STREAMINFO block.\n");
579     fprintf(out, "--show-max-framesize  Show the maximum frame size from the STREAMINFO block.\n");
580     fprintf(out, "--show-sample-rate    Show the sample rate from the STREAMINFO block.\n");
581     fprintf(out, "--show-channels       Show the number of channels from the STREAMINFO block.\n");
582     fprintf(out, "--show-bps            Show the # of bits per sample from the STREAMINFO block.\n");
583     fprintf(out, "--show-total-samples  Show the total # of samples from the STREAMINFO block.\n");
584     fprintf(out, "\n");
585     fprintf(out, "--show-vc-vendor      Show the vendor string from the VORBIS_COMMENT block.\n");
586     fprintf(out, "--show-vc-field=name  Show all Vorbis comment fields where the the field name\n");
587     fprintf(out, "                      matches 'name'.\n");
588     fprintf(out, "--remove-vc-field=name\n");
589     fprintf(out, "                      Remove all Vorbis comment fields whose field name is\n");
590     fprintf(out, "                      'name'.\n");
591     fprintf(out, "--remove-vc-firstfield=name\n");
592     fprintf(out, "                      Remove first Vorbis comment field whose field name is\n");
593     fprintf(out, "                      'name'.\n");
594     fprintf(out, "--remove-vc-all       Remove all Vorbis comment fields, leaving only the\n");
595     fprintf(out, "                      vendor string in the VORBIS_COMMENT block.\n");
596     fprintf(out, "--set-vc-field=field  Add a Vorbis comment field.  The field must comply with\n");
597     fprintf(out, "                      the Vorbis comment spec, of the form \"NAME=VALUE\".  If\n");
598     fprintf(out, "                      there is currently no VORBIS_COMMENT block, one will be\n");
599     fprintf(out, "                      created.\n");
600     fprintf(out, "\n");
601     fprintf(out, "Major operations:\n");
602     fprintf(out, "--list : List the contents of one or more metadata blocks to stdout.  By\n");
603     fprintf(out, "         default, all metadata blocks are listed in text format.  Use the\n");
604     fprintf(out, "         following options to change this behavior:\n");
605     fprintf(out, "\n");
606     fprintf(out, "    --block-number=#[,#[...]]\n");
607     fprintf(out, "    An optional comma-separated list of block numbers to display.  The first\n");
608     fprintf(out, "    block, the STREAMINFO block, is block 0.\n");
609     fprintf(out, "\n");
610     fprintf(out, "    --block-type=type[,type[...]]\n");
611     fprintf(out, "    --except-block-type=type[,type[...]]\n");
612     fprintf(out, "    An optional comma-separated list of block types to included or ignored\n");
613     fprintf(out, "    with this option.  Use only one of --block-type or --except-block-type.\n");
614     fprintf(out, "    The valid block types are: STREAMINFO, PADDING, APPLICATION, SEEKTABLE,\n");
615     fprintf(out, "    VORBIS_COMMENT.  You may narrow down the types of APPLICATION blocks\n");
616     fprintf(out, "    displayed as follows:\n");
617     fprintf(out, "        APPLICATION:abcd        The APPLICATION block(s) whose textual repre-\n");
618     fprintf(out, "                                sentation of the 4-byte ID is \"abcd\"\n");
619     fprintf(out, "        APPLICATION:0xXXXXXXXX  The APPLICATION block(s) whose hexadecimal big-\n");
620     fprintf(out, "                                endian representation of the 4-byte ID is\n");
621     fprintf(out, "                                \"0xXXXXXXXX\".  For the example \"abcd\" above the\n");
622     fprintf(out, "                                hexadecimal equivalalent is 0x61626364\n");
623     fprintf(out, "\n");
624     fprintf(out, "    NOTE: if both --block-number and --[except-]block-type are specified,\n");
625     fprintf(out, "          the result is the logical AND of both arguments.\n");
626     fprintf(out, "\n");
627 #if 0
628         /*@@@ not implemented yet */
629     fprintf(out, "    --data-format=binary|text\n");
630     fprintf(out, "    By default a human-readable text representation of the data is displayed.\n");
631     fprintf(out, "    You may specify --data-format=binary to dump the raw binary form of each\n");
632     fprintf(out, "    metadata block.  The output can be read in using a subsequent call to\n");
633     fprintf(out, "    "metaflac --append --from-file=..."\n");
634 #endif
635     fprintf(out, "\n");
636     fprintf(out, "    --application-data-format=hexdump|text\n");
637     fprintf(out, "    If the application block you are displaying contains binary data but your\n");
638     fprintf(out, "    --data-format=text, you can display a hex dump of the application data\n");
639     fprintf(out, "    contents instead using --application-data-format=hexdump\n");
640     fprintf(out, "\n");
641 #if 0
642         /*@@@ not implemented yet */
643     fprintf(out, "--append : Insert a metadata block from a file.  The input file must be in the\n");
644     fprintf(out, "           same format as generated with --list.\n");
645     fprintf(out, "\n");
646     fprintf(out, "    --block-number=#\n");
647     fprintf(out, "    Specify the insertion point (defaults to last block).  The new block will\n");
648     fprintf(out, "    be added after the given block number.  This prevents the illegal insertion\n");
649     fprintf(out, "    of a block before the first STREAMINFO block.  You may not --append another\n");
650     fprintf(out, "    STREAMINFO block.\n");
651     fprintf(out, "\n");
652     fprintf(out, "    --from-file=filename\n");
653     fprintf(out, "    Mandatory 'option' to specify the input file containing the block contents.\n");
654     fprintf(out, "\n");
655     fprintf(out, "    --data-format=binary|text\n");
656     fprintf(out, "    By default the block contents are assumed to be in binary format.  You can\n");
657     fprintf(out, "    override this by specifying --data-format=text\n");
658     fprintf(out, "\n");
659 #endif
660     fprintf(out, "--remove : Remove one or more metadata blocks from the metadata.  Unless\n");
661     fprintf(out, "           --dont-use-padding is specified, the blocks will be replaced with\n");
662     fprintf(out, "           padding.  You may not remove the STREAMINFO block.\n");
663     fprintf(out, "\n");
664     fprintf(out, "    --block-number=#[,#[...]]\n");
665     fprintf(out, "    --block-type=type[,type[...]]\n");
666     fprintf(out, "    --except-block-type=type[,type[...]]\n");
667     fprintf(out, "    See --list above for usage.\n");
668     fprintf(out, "\n");
669     fprintf(out, "    NOTE: if both --block-number and --[except-]block-type are specified,\n");
670     fprintf(out, "          the result is the logical AND of both arguments.\n");
671     fprintf(out, "\n");
672     fprintf(out, "--remove-all : Remove all metadata blocks (except the STREAMINFO block) from\n");
673     fprintf(out, "               the metadata.  Unless --dont-use-padding is specified, the\n");
674     fprintf(out, "               blocks will be replaced with padding.\n");
675     fprintf(out, "\n");
676     fprintf(out, "--merge-padding : Merge adjacent PADDING blocks into single blocks.\n");
677     fprintf(out, "\n");
678     fprintf(out, "--sort-padding : Move all PADDING blocks to the end of the metadata and merge\n");
679     fprintf(out, "                 them into a single block.\n");
680
681         return message? 1 : 0;
682 }
683
684 void hexdump(const FLAC__byte *buf, unsigned bytes, const char *indent)
685 {
686         unsigned i, left = bytes;
687         const FLAC__byte *b = buf;
688
689         for(i = 0; i < bytes; i += 16) {
690                 printf("%s%08X: "
691                         "%02X %02X %02X %02X %02X %02X %02X %02X "
692                         "%02X %02X %02X %02X %02X %02X %02X %02X "
693                         "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n",
694                         indent, i,
695                         left >  0? (unsigned char)b[ 0] : 0,
696                         left >  1? (unsigned char)b[ 1] : 0,
697                         left >  2? (unsigned char)b[ 2] : 0,
698                         left >  3? (unsigned char)b[ 3] : 0,
699                         left >  4? (unsigned char)b[ 4] : 0,
700                         left >  5? (unsigned char)b[ 5] : 0,
701                         left >  6? (unsigned char)b[ 6] : 0,
702                         left >  7? (unsigned char)b[ 7] : 0,
703                         left >  8? (unsigned char)b[ 8] : 0,
704                         left >  9? (unsigned char)b[ 9] : 0,
705                         left > 10? (unsigned char)b[10] : 0,
706                         left > 11? (unsigned char)b[11] : 0,
707                         left > 12? (unsigned char)b[12] : 0,
708                         left > 13? (unsigned char)b[13] : 0,
709                         left > 14? (unsigned char)b[14] : 0,
710                         left > 15? (unsigned char)b[15] : 0,
711                         (left >  0) ? (isprint(b[ 0]) ? b[ 0] : '.') : ' ',
712                         (left >  1) ? (isprint(b[ 1]) ? b[ 1] : '.') : ' ',
713                         (left >  2) ? (isprint(b[ 2]) ? b[ 2] : '.') : ' ',
714                         (left >  3) ? (isprint(b[ 3]) ? b[ 3] : '.') : ' ',
715                         (left >  4) ? (isprint(b[ 4]) ? b[ 4] : '.') : ' ',
716                         (left >  5) ? (isprint(b[ 5]) ? b[ 5] : '.') : ' ',
717                         (left >  6) ? (isprint(b[ 6]) ? b[ 6] : '.') : ' ',
718                         (left >  7) ? (isprint(b[ 7]) ? b[ 7] : '.') : ' ',
719                         (left >  8) ? (isprint(b[ 8]) ? b[ 8] : '.') : ' ',
720                         (left >  9) ? (isprint(b[ 9]) ? b[ 9] : '.') : ' ',
721                         (left > 10) ? (isprint(b[10]) ? b[10] : '.') : ' ',
722                         (left > 11) ? (isprint(b[11]) ? b[11] : '.') : ' ',
723                         (left > 12) ? (isprint(b[12]) ? b[12] : '.') : ' ',
724                         (left > 13) ? (isprint(b[13]) ? b[13] : '.') : ' ',
725                         (left > 14) ? (isprint(b[14]) ? b[14] : '.') : ' ',
726                         (left > 15) ? (isprint(b[15]) ? b[15] : '.') : ' '
727                 );
728                 left -= 16;
729                 b += 16;
730    }
731 }
732
733 char *local_strdup(const char *source)
734 {
735         char *ret;
736         FLAC__ASSERT(0 != source);
737         if(0 == (ret = strdup(source))) {
738                 fprintf(stderr, "ERROR: out of memory during strdup()\n");
739                 exit(1);
740         }
741         return ret;
742 }
743
744 FLAC__bool parse_vorbis_comment_field(const char *field, char **name, char **value, unsigned *length)
745 {
746         char *p, *s = local_strdup(field);
747
748         if(0 == (p = strchr(s, '='))) {
749                 free(s);
750                 return false;
751         }
752         *p++ = '\0';
753         *name = local_strdup(s);
754         *value = local_strdup(p);
755         *length = strlen(p);
756
757         free(s);
758         return true;
759 }
760
761 FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out)
762 {
763         char *p, *q, *s;
764         int i;
765         unsigned entry;
766
767         if(*in == '\0')
768                 return false;
769
770         s = local_strdup(in);
771
772         /* first count the entries */
773         for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(s, ','))
774                 ;
775
776         /* make space */
777         FLAC__ASSERT(out->num_entries > 0);
778         if(0 == (out->entries = malloc(sizeof(unsigned) * out->num_entries))) {
779                 fprintf(stderr, "ERROR: out of memory allocating space for option list\n");
780                 exit(1);
781         }
782
783         /* load 'em up */
784         entry = 0;
785         q = s;
786         while(q) {
787                 if(0 != (p = strchr(q, ',')))
788                         *p++ = 0;
789                 if(!isdigit((int)(*q)) || (i = atoi(q)) < 0) {
790                         free(s);
791                         return false;
792                 }
793                 FLAC__ASSERT(entry < out->num_entries);
794                 out->entries[entry++] = (unsigned)i;
795                 q = p;
796         }
797         FLAC__ASSERT(entry == out->num_entries);
798
799         free(s);
800         return true;
801 }
802
803 FLAC__bool parse_block_type(const char *in, Argument_BlockType *out)
804 {
805         return true;
806 }
807
808 FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out)
809 {
810         if(0 == strcmp(in, "binary"))
811                 out->is_binary = true;
812         else if(0 == strcmp(in, "text"))
813                 out->is_binary = false;
814         else
815                 return false;
816         return true;
817 }
818
819 FLAC__bool parse_application_data_format(const char *in, Argument_ApplicationDataFormat *out)
820 {
821         if(0 == strcmp(in, "hexdump"))
822                 out->is_hexdump = true;
823         else if(0 == strcmp(in, "text"))
824                 out->is_hexdump = false;
825         else
826                 return false;
827         return true;
828 }