add UTF-8 support
[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 "share/utf8.h"
31 #include <ctype.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #if 0
38 /*[JEC] was:#if HAVE_GETOPT_LONG*/
39 /*[JEC] see flac/include/share/getopt.h as to why the change */
40 #  include <getopt.h>
41 #else
42 #  include "share/getopt.h"
43 #endif
44
45 /*
46    FLAC__share__getopt format struct; note we don't use short options so we just
47    set the 'val' field to 0 everywhere to indicate a valid option.
48 */
49 static struct FLAC__share__option long_options_[] = {
50         /* global options */
51     { "preserve-modtime", 0, 0, 0 },
52     { "with-filename", 0, 0, 0 },
53     { "no-filename", 0, 0, 0 },
54     { "no-utf8-convert", 0, 0, 0 },
55     { "dont-use-padding", 0, 0, 0 },
56         /* shorthand operations */
57     { "show-md5sum", 0, 0, 0 },
58     { "show-min-blocksize", 0, 0, 0 },
59     { "show-max-blocksize", 0, 0, 0 },
60     { "show-min-framesize", 0, 0, 0 },
61     { "show-max-framesize", 0, 0, 0 },
62     { "show-sample-rate", 0, 0, 0 },
63     { "show-channels", 0, 0, 0 },
64     { "show-bps", 0, 0, 0 },
65     { "show-total-samples", 0, 0, 0 },
66     { "show-vc-vendor", 0, 0, 0 },
67     { "show-vc-field", 1, 0, 0 },
68     { "remove-vc-all", 0, 0, 0 },
69     { "remove-vc-field", 1, 0, 0 },
70     { "remove-vc-firstfield", 1, 0, 0 },
71     { "set-vc-field", 1, 0, 0 },
72     { "add-padding", 1, 0, 0 },
73         /* major operations */
74     { "help", 0, 0, 0 },
75     { "list", 0, 0, 0 },
76     { "append", 0, 0, 0 },
77     { "remove", 0, 0, 0 },
78     { "remove-all", 0, 0, 0 },
79     { "merge-padding", 0, 0, 0 },
80     { "sort-padding", 0, 0, 0 },
81         /* major operation arguments */
82     { "block-number", 1, 0, 0 },
83     { "block-type", 1, 0, 0 },
84     { "except-block-type", 1, 0, 0 },
85     { "data-format", 1, 0, 0 },
86     { "application-data-format", 1, 0, 0 },
87     { "from-file", 1, 0, 0 },
88     {0, 0, 0, 0}
89 };
90
91 typedef enum {
92         OP__SHOW_MD5SUM,
93         OP__SHOW_MIN_BLOCKSIZE,
94         OP__SHOW_MAX_BLOCKSIZE,
95         OP__SHOW_MIN_FRAMESIZE,
96         OP__SHOW_MAX_FRAMESIZE,
97         OP__SHOW_SAMPLE_RATE,
98         OP__SHOW_CHANNELS,
99         OP__SHOW_BPS,
100         OP__SHOW_TOTAL_SAMPLES,
101         OP__SHOW_VC_VENDOR,
102         OP__SHOW_VC_FIELD,
103         OP__REMOVE_VC_ALL,
104         OP__REMOVE_VC_FIELD,
105         OP__REMOVE_VC_FIRSTFIELD,
106         OP__SET_VC_FIELD,
107         OP__ADD_PADDING,
108         OP__LIST,
109         OP__APPEND,
110         OP__REMOVE,
111         OP__REMOVE_ALL,
112         OP__MERGE_PADDING,
113         OP__SORT_PADDING
114 } OperationType;
115
116 typedef enum {
117         ARG__BLOCK_NUMBER,
118         ARG__BLOCK_TYPE,
119         ARG__EXCEPT_BLOCK_TYPE,
120         ARG__DATA_FORMAT,
121         ARG__FROM_FILE
122 } ArgumentType;
123
124 typedef struct {
125         char *field_name;
126 } Argument_VcFieldName;
127
128 typedef struct {
129         char *field; /* the whole field as passed on the command line, i.e. "NAME=VALUE" */
130         char *field_name;
131         /* according to the vorbis spec, field values can contain \0 so simple C strings are not enough here */
132         unsigned field_value_length;
133         char *field_value;
134 } Argument_VcField;
135
136 typedef struct {
137         unsigned num_entries;
138         unsigned *entries;
139 } Argument_BlockNumber;
140
141 typedef struct {
142         FLAC__MetadataType type;
143         char application_id[4]; /* only relevant if type == FLAC__STREAM_METADATA_TYPE_APPLICATION */
144         FLAC__bool filter_application_by_id;
145 } Argument_BlockTypeEntry;
146
147 typedef struct {
148         unsigned num_entries;
149         Argument_BlockTypeEntry *entries;
150 } Argument_BlockType;
151
152 typedef struct {
153         FLAC__bool is_binary;
154 } Argument_DataFormat;
155
156 typedef struct {
157         char *file_name;
158 } Argument_FromFile;
159
160 typedef struct {
161         unsigned length;
162 } Argument_AddPadding;
163
164 typedef struct {
165         OperationType type;
166         union {
167                 Argument_VcFieldName show_vc_field;
168                 Argument_VcFieldName remove_vc_field;
169                 Argument_VcFieldName remove_vc_firstfield;
170                 Argument_VcField set_vc_field;
171                 Argument_AddPadding add_padding;
172         } argument;
173 } Operation;
174
175 typedef struct {
176         ArgumentType type;
177         union {
178                 Argument_BlockNumber block_number;
179                 Argument_BlockType block_type;
180                 Argument_DataFormat data_format;
181                 Argument_FromFile from_file;
182         } value;
183 } Argument;
184
185 typedef struct {
186         FLAC__bool preserve_modtime;
187         FLAC__bool prefix_with_filename;
188         FLAC__bool utf8_convert;
189         FLAC__bool use_padding;
190         FLAC__bool show_long_help;
191         FLAC__bool application_data_format_is_hexdump;
192         struct {
193                 Operation *operations;
194                 unsigned num_operations;
195                 unsigned capacity;
196         } ops;
197         struct {
198                 struct {
199                         unsigned num_shorthand_ops;
200                         unsigned num_major_ops;
201                         FLAC__bool has_block_type;
202                         FLAC__bool has_except_block_type;
203                 } checks;
204                 Argument *arguments;
205                 unsigned num_arguments;
206                 unsigned capacity;
207         } args;
208         unsigned num_files;
209         char **filenames;
210 } CommandLineOptions;
211
212 static void die(const char *message);
213 static void init_options(CommandLineOptions *options);
214 static FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options);
215 static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options);
216 static void free_options(CommandLineOptions *options);
217 static void append_new_operation(CommandLineOptions *options, Operation operation);
218 static void append_new_argument(CommandLineOptions *options, Argument argument);
219 static Operation *append_major_operation(CommandLineOptions *options, OperationType type);
220 static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type);
221 static Argument *append_argument(CommandLineOptions *options, ArgumentType type);
222 static int short_usage(const char *message, ...);
223 static int long_usage(const char *message, ...);
224 static char *local_strdup(const char *source);
225 static FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation);
226 static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation);
227 static FLAC__bool parse_add_padding(const char *in, unsigned *out);
228 static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out);
229 static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out);
230 static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out);
231 static FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out);
232 static FLAC__bool do_operations(const CommandLineOptions *options);
233 static FLAC__bool do_major_operation(const CommandLineOptions *options);
234 static FLAC__bool do_major_operation_on_file(const char *filename, const CommandLineOptions *options);
235 static FLAC__bool do_major_operation__list(const char *filename, FLAC__Metadata_Chain *chain, const CommandLineOptions *options);
236 static FLAC__bool do_major_operation__append(FLAC__Metadata_Chain *chain, const CommandLineOptions *options);
237 static FLAC__bool do_major_operation__remove(FLAC__Metadata_Chain *chain, const CommandLineOptions *options);
238 static FLAC__bool do_major_operation__remove_all(FLAC__Metadata_Chain *chain, const CommandLineOptions *options);
239 static FLAC__bool do_shorthand_operations(const CommandLineOptions *options);
240 static FLAC__bool do_shorthand_operations_on_file(const char *filename, const CommandLineOptions *options);
241 static FLAC__bool do_shorthand_operation(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool utf8_convert);
242 static FLAC__bool do_shorthand_operation__add_padding(const char *filename, FLAC__Metadata_Chain *chain, unsigned length, FLAC__bool *needs_write);
243 static FLAC__bool do_shorthand_operation__streaminfo(const char *filename, FLAC__Metadata_Chain *chain, OperationType op);
244 static FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool raw);
245 static FLAC__bool passes_filter(const CommandLineOptions *options, const FLAC__StreamMetadata *block, unsigned block_number);
246 static void write_metadata(const char *filename, FLAC__StreamMetadata *block, unsigned block_number, FLAC__bool hexdump_application);
247 static void write_vc_field(const char *filename, const FLAC__StreamMetadata_VorbisComment_Entry *entry, FLAC__bool raw);
248 static void write_vc_fields(const char *filename, const char *field_name, const FLAC__StreamMetadata_VorbisComment_Entry entry[], unsigned num_entries, FLAC__bool raw);
249 static FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write);
250 static FLAC__bool remove_vc_field(FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
251 static FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
252 static FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw);
253 static FLAC__bool field_name_matches_entry(const char *field_name, unsigned field_name_length, const FLAC__StreamMetadata_VorbisComment_Entry *entry);
254 static void hexdump(const char *filename, const FLAC__byte *buf, unsigned bytes, const char *indent);
255
256 int main(int argc, char *argv[])
257 {
258         CommandLineOptions options;
259         int ret = 0;
260
261         init_options(&options);
262
263         if(parse_options(argc, argv, &options))
264                 ret = !do_operations(&options);
265
266         free_options(&options);
267
268         return ret;
269 }
270
271 void die(const char *message)
272 {
273         FLAC__ASSERT(0 != message);
274         fprintf(stderr, "ERROR: %s\n", message);
275         exit(1);
276 }
277
278 void init_options(CommandLineOptions *options)
279 {
280         options->preserve_modtime = false;
281
282         /* '2' is a hack to mean "use default if not forced on command line" */
283         FLAC__ASSERT(true != 2);
284         options->prefix_with_filename = 2;
285
286         options->utf8_convert = true;
287         options->use_padding = true;
288         options->show_long_help = false;
289         options->application_data_format_is_hexdump = false;
290
291         options->ops.operations = 0;
292         options->ops.num_operations = 0;
293         options->ops.capacity = 0;
294
295         options->args.arguments = 0;
296         options->args.num_arguments = 0;
297         options->args.capacity = 0;
298
299         options->args.checks.num_shorthand_ops = 0;
300         options->args.checks.num_major_ops = 0;
301         options->args.checks.has_block_type = false;
302         options->args.checks.has_except_block_type = false;
303
304         options->num_files = 0;
305         options->filenames = 0;
306 }
307
308 FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options)
309 {
310     int ret;
311     int option_index = 1;
312         FLAC__bool had_error = false;
313
314     while ((ret = FLAC__share__getopt_long(argc, argv, "", long_options_, &option_index)) != -1) {
315         switch (ret) {
316             case 0:
317                                 had_error |= !parse_option(option_index, FLAC__share__optarg, options);
318                 break;
319                         case '?':
320                         case ':':
321                 had_error = true;
322                 break;
323             default:
324                                 FLAC__ASSERT(0);
325                                 break;
326         }
327     }
328
329         if(options->prefix_with_filename == 2)
330                 options->prefix_with_filename = (argc - FLAC__share__optind > 1);
331
332         if(FLAC__share__optind >= argc && !options->show_long_help) {
333                 fprintf(stderr,"ERROR: you must specify at least one FLAC file;\n");
334                 fprintf(stderr,"       metaflac cannot be used as a pipe\n");
335                 had_error = true;
336         }
337
338         options->num_files = argc - FLAC__share__optind;
339
340         if(options->num_files > 0) {
341                 unsigned i = 0;
342                 if(0 == (options->filenames = malloc(sizeof(char *) * options->num_files)))
343                         die("out of memory allocating space for file names list");
344                 while(FLAC__share__optind < argc)
345                         options->filenames[i++] = local_strdup(argv[FLAC__share__optind++]);
346         }
347
348         if(options->args.checks.num_major_ops > 0) {
349                 if(options->args.checks.num_major_ops > 1) {
350                         fprintf(stderr, "ERROR: you may only specify one major operation at a time\n");
351                         had_error = true;
352                 }
353                 else if(options->args.checks.num_shorthand_ops > 0) {
354                         fprintf(stderr, "ERROR: you may not mix shorthand and major operations\n");
355                         had_error = true;
356                 }
357         }
358
359         if(options->args.checks.has_block_type && options->args.checks.has_except_block_type) {
360                 fprintf(stderr, "ERROR: you may not specify both '--block-type' and '--except-block-type'\n");
361                 had_error = true;
362         }
363
364         if(had_error)
365                 short_usage(0);
366
367         return !had_error;
368 }
369
370 FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options)
371 {
372         const char *opt = long_options_[option_index].name;
373         Operation *op;
374         Argument *arg;
375         FLAC__bool ok = true;
376
377     if(0 == strcmp(opt, "preserve-modtime")) {
378                 options->preserve_modtime = true;
379         }
380     else if(0 == strcmp(opt, "with-filename")) {
381                 options->prefix_with_filename = true;
382         }
383     else if(0 == strcmp(opt, "no-filename")) {
384                 options->prefix_with_filename = false;
385         }
386     else if(0 == strcmp(opt, "no-utf8-convert")) {
387                 options->utf8_convert = false;
388         }
389     else if(0 == strcmp(opt, "dont-use-padding")) {
390                 options->use_padding = false;
391         }
392     else if(0 == strcmp(opt, "show-md5sum")) {
393                 (void) append_shorthand_operation(options, OP__SHOW_MD5SUM);
394         }
395     else if(0 == strcmp(opt, "show-min-blocksize")) {
396                 (void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE);
397         }
398     else if(0 == strcmp(opt, "show-max-blocksize")) {
399                 (void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE);
400         }
401     else if(0 == strcmp(opt, "show-min-framesize")) {
402                 (void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE);
403         }
404     else if(0 == strcmp(opt, "show-max-framesize")) {
405                 (void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE);
406         }
407     else if(0 == strcmp(opt, "show-sample-rate")) {
408                 (void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE);
409         }
410     else if(0 == strcmp(opt, "show-channels")) {
411                 (void) append_shorthand_operation(options, OP__SHOW_CHANNELS);
412         }
413     else if(0 == strcmp(opt, "show-bps")) {
414                 (void) append_shorthand_operation(options, OP__SHOW_BPS);
415         }
416     else if(0 == strcmp(opt, "show-total-samples")) {
417                 (void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES);
418         }
419     else if(0 == strcmp(opt, "show-vc-vendor")) {
420                 (void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR);
421         }
422     else if(0 == strcmp(opt, "show-vc-field")) {
423                 const char *violation;
424                 op = append_shorthand_operation(options, OP__SHOW_VC_FIELD);
425                 FLAC__ASSERT(0 != option_argument);
426                 if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.show_vc_field.field_name), &violation)) {
427                         FLAC__ASSERT(0 != violation);
428                         fprintf(stderr, "ERROR: malformed vorbis comment field name \"%s\",\n       %s\n", option_argument, violation);
429                         ok = false;
430                 }
431         }
432     else if(0 == strcmp(opt, "remove-vc-all")) {
433                 (void) append_shorthand_operation(options, OP__REMOVE_VC_ALL);
434         }
435     else if(0 == strcmp(opt, "remove-vc-field")) {
436                 const char *violation;
437                 op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
438                 FLAC__ASSERT(0 != option_argument);
439                 if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.remove_vc_field.field_name), &violation)) {
440                         FLAC__ASSERT(0 != violation);
441                         fprintf(stderr, "ERROR: malformed vorbis comment field name \"%s\",\n       %s\n", option_argument, violation);
442                         ok = false;
443                 }
444         }
445     else if(0 == strcmp(opt, "remove-vc-firstfield")) {
446                 const char *violation;
447                 op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD);
448                 FLAC__ASSERT(0 != option_argument);
449                 if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.remove_vc_firstfield.field_name), &violation)) {
450                         FLAC__ASSERT(0 != violation);
451                         fprintf(stderr, "ERROR: malformed vorbis comment field name \"%s\",\n       %s\n", option_argument, violation);
452                         ok = false;
453                 }
454         }
455     else if(0 == strcmp(opt, "set-vc-field")) {
456                 const char *violation;
457                 op = append_shorthand_operation(options, OP__SET_VC_FIELD);
458                 FLAC__ASSERT(0 != option_argument);
459                 if(!parse_vorbis_comment_field(option_argument, &(op->argument.set_vc_field.field), &(op->argument.set_vc_field.field_name), &(op->argument.set_vc_field.field_value), &(op->argument.set_vc_field.field_value_length), &violation)) {
460                         FLAC__ASSERT(0 != violation);
461                         fprintf(stderr, "ERROR: malformed vorbis comment field \"%s\",\n       %s\n", option_argument, violation);
462                         ok = false;
463                 }
464         }
465     else if(0 == strcmp(opt, "add-padding")) {
466                 op = append_shorthand_operation(options, OP__ADD_PADDING);
467                 FLAC__ASSERT(0 != option_argument);
468                 if(!parse_add_padding(option_argument, &(op->argument.add_padding.length))) {
469                         fprintf(stderr, "ERROR: illegal length \"%s\", length must be >= 0 and < 2^%u\n", option_argument, FLAC__STREAM_METADATA_LENGTH_LEN);
470                         ok = false;
471                 }
472         }
473     else if(0 == strcmp(opt, "help")) {
474                 options->show_long_help = true;
475         }
476     else if(0 == strcmp(opt, "list")) {
477                 (void) append_major_operation(options, OP__LIST);
478         }
479     else if(0 == strcmp(opt, "append")) {
480                 (void) append_major_operation(options, OP__APPEND);
481         }
482     else if(0 == strcmp(opt, "remove")) {
483                 (void) append_major_operation(options, OP__REMOVE);
484         }
485     else if(0 == strcmp(opt, "remove-all")) {
486                 (void) append_major_operation(options, OP__REMOVE_ALL);
487         }
488     else if(0 == strcmp(opt, "merge-padding")) {
489                 (void) append_major_operation(options, OP__MERGE_PADDING);
490         }
491     else if(0 == strcmp(opt, "sort-padding")) {
492                 (void) append_major_operation(options, OP__SORT_PADDING);
493         }
494     else if(0 == strcmp(opt, "block-number")) {
495                 arg = append_argument(options, ARG__BLOCK_NUMBER);
496                 FLAC__ASSERT(0 != option_argument);
497                 if(!parse_block_number(option_argument, &(arg->value.block_number))) {
498                         fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument);
499                         ok = false;
500                 }
501         }
502     else if(0 == strcmp(opt, "block-type")) {
503                 arg = append_argument(options, ARG__BLOCK_TYPE);
504                 FLAC__ASSERT(0 != option_argument);
505                 if(!parse_block_type(option_argument, &(arg->value.block_type))) {
506                         fprintf(stderr, "ERROR: malformed block type specification \"%s\"\n", option_argument);
507                         ok = false;
508                 }
509                 options->args.checks.has_block_type = true;
510         }
511     else if(0 == strcmp(opt, "except-block-type")) {
512                 arg = append_argument(options, ARG__EXCEPT_BLOCK_TYPE);
513                 FLAC__ASSERT(0 != option_argument);
514                 if(!parse_block_type(option_argument, &(arg->value.block_type))) {
515                         fprintf(stderr, "ERROR: malformed block type specification \"%s\"\n", option_argument);
516                         ok = false;
517                 }
518                 options->args.checks.has_except_block_type = true;
519         }
520     else if(0 == strcmp(opt, "data-format")) {
521                 arg = append_argument(options, ARG__DATA_FORMAT);
522                 FLAC__ASSERT(0 != option_argument);
523                 if(!parse_data_format(option_argument, &(arg->value.data_format))) {
524                         fprintf(stderr, "ERROR: illegal data format \"%s\"\n", option_argument);
525                         ok = false;
526                 }
527         }
528     else if(0 == strcmp(opt, "application-data-format")) {
529                 FLAC__ASSERT(0 != option_argument);
530                 if(!parse_application_data_format(option_argument, &(options->application_data_format_is_hexdump))) {
531                         fprintf(stderr, "ERROR: illegal application data format \"%s\"\n", option_argument);
532                         ok = false;
533                 }
534         }
535     else if(0 == strcmp(opt, "from-file")) {
536                 arg = append_argument(options, ARG__FROM_FILE);
537                 FLAC__ASSERT(0 != option_argument);
538                 arg->value.from_file.file_name = local_strdup(option_argument);
539         }
540         else {
541                 FLAC__ASSERT(0);
542         }
543
544         return ok;
545 }
546
547 void free_options(CommandLineOptions *options)
548 {
549         unsigned i;
550         Operation *op;
551         Argument *arg;
552
553         FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0);
554         FLAC__ASSERT(0 == options->args.arguments || options->args.num_arguments > 0);
555
556         for(i = 0, op = options->ops.operations; i < options->ops.num_operations; i++, op++) {
557                 switch(op->type) {
558                         case OP__SHOW_VC_FIELD:
559                         case OP__REMOVE_VC_FIELD:
560                         case OP__REMOVE_VC_FIRSTFIELD:
561                                 if(0 != op->argument.show_vc_field.field_name)
562                                         free(op->argument.show_vc_field.field_name);
563                                 break;
564                         case OP__SET_VC_FIELD:
565                                 if(0 != op->argument.set_vc_field.field)
566                                         free(op->argument.set_vc_field.field);
567                                 if(0 != op->argument.set_vc_field.field_name)
568                                         free(op->argument.set_vc_field.field_name);
569                                 if(0 != op->argument.set_vc_field.field_value)
570                                         free(op->argument.set_vc_field.field_value);
571                                 break;
572                         default:
573                                 break;
574                 }
575         }
576
577         for(i = 0, arg = options->args.arguments; i < options->args.num_arguments; i++, arg++) {
578                 switch(arg->type) {
579                         case ARG__BLOCK_NUMBER:
580                                 if(0 != arg->value.block_number.entries)
581                                         free(arg->value.block_number.entries);
582                                 break;
583                         case ARG__BLOCK_TYPE:
584                         case ARG__EXCEPT_BLOCK_TYPE:
585                                 if(0 != arg->value.block_type.entries)
586                                         free(arg->value.block_type.entries);
587                                 break;
588                         case ARG__FROM_FILE:
589                                 if(0 != arg->value.from_file.file_name)
590                                         free(arg->value.from_file.file_name);
591                                 break;
592                         default:
593                                 break;
594                 }
595         }
596
597         if(0 != options->ops.operations)
598                 free(options->ops.operations);
599
600         if(0 != options->args.arguments)
601                 free(options->args.arguments);
602
603         if(0 != options->filenames)
604                 free(options->filenames);
605 }
606
607 void append_new_operation(CommandLineOptions *options, Operation operation)
608 {
609         if(options->ops.capacity == 0) {
610                 options->ops.capacity = 50;
611                 if(0 == (options->ops.operations = malloc(sizeof(Operation) * options->ops.capacity)))
612                         die("out of memory allocating space for option list");
613                 memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity);
614         }
615         if(options->ops.capacity <= options->ops.num_operations) {
616                 unsigned original_capacity = options->ops.capacity;
617                 options->ops.capacity *= 4;
618                 if(0 == (options->ops.operations = realloc(options->ops.operations, sizeof(Operation) * options->ops.capacity)))
619                         die("out of memory allocating space for option list");
620                 memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity));
621         }
622
623         options->ops.operations[options->ops.num_operations++] = operation;
624 }
625
626 void append_new_argument(CommandLineOptions *options, Argument argument)
627 {
628         if(options->args.capacity == 0) {
629                 options->args.capacity = 50;
630                 if(0 == (options->args.arguments = malloc(sizeof(Argument) * options->args.capacity)))
631                         die("out of memory allocating space for option list");
632                 memset(options->args.arguments, 0, sizeof(Argument) * options->args.capacity);
633         }
634         if(options->args.capacity <= options->args.num_arguments) {
635                 unsigned original_capacity = options->args.capacity;
636                 options->args.capacity *= 4;
637                 if(0 == (options->args.arguments = realloc(options->args.arguments, sizeof(Argument) * options->args.capacity)))
638                         die("out of memory allocating space for option list");
639                 memset(options->args.arguments + original_capacity, 0, sizeof(Argument) * (options->args.capacity - original_capacity));
640         }
641
642         options->args.arguments[options->args.num_arguments++] = argument;
643 }
644
645 Operation *append_major_operation(CommandLineOptions *options, OperationType type)
646 {
647         Operation op;
648         memset(&op, 0, sizeof(op));
649         op.type = type;
650         append_new_operation(options, op);
651         options->args.checks.num_major_ops++;
652         return options->ops.operations + (options->ops.num_operations - 1);
653 }
654
655 Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type)
656 {
657         Operation op;
658         memset(&op, 0, sizeof(op));
659         op.type = type;
660         append_new_operation(options, op);
661         options->args.checks.num_shorthand_ops++;
662         return options->ops.operations + (options->ops.num_operations - 1);
663 }
664
665 Argument *append_argument(CommandLineOptions *options, ArgumentType type)
666 {
667         Argument arg;
668         memset(&arg, 0, sizeof(arg));
669         arg.type = type;
670         append_new_argument(options, arg);
671         return options->args.arguments + (options->args.num_arguments - 1);
672 }
673
674 static void usage_header(FILE *out)
675 {
676         fprintf(out, "==============================================================================\n");
677         fprintf(out, "metaflac - Command-line FLAC metadata editor version %s\n", FLAC__VERSION_STRING);
678         fprintf(out, "Copyright (C) 2001,2002  Josh Coalson\n");
679         fprintf(out, "\n");
680         fprintf(out, "This program is free software; you can redistribute it and/or\n");
681         fprintf(out, "modify it under the terms of the GNU General Public License\n");
682         fprintf(out, "as published by the Free Software Foundation; either version 2\n");
683         fprintf(out, "of the License, or (at your option) any later version.\n");
684         fprintf(out, "\n");
685         fprintf(out, "This program is distributed in the hope that it will be useful,\n");
686         fprintf(out, "but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
687         fprintf(out, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
688         fprintf(out, "GNU General Public License for more details.\n");
689         fprintf(out, "\n");
690         fprintf(out, "You should have received a copy of the GNU General Public License\n");
691         fprintf(out, "along with this program; if not, write to the Free Software\n");
692         fprintf(out, "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n");
693         fprintf(out, "==============================================================================\n");
694 }
695
696 static void usage_summary(FILE *out)
697 {
698     fprintf(out, "Usage:\n");
699     fprintf(out, "  metaflac [options] [operations] FLACfile [FLACfile ...]\n");
700     fprintf(out, "\n");
701     fprintf(out, "Use metaflac to list, add, remove, or edit metadata in one or more FLAC files.\n");
702     fprintf(out, "You may perform one major operation, or many shorthand operations at a time.\n");
703     fprintf(out, "\n");
704     fprintf(out, "Options:\n");
705     fprintf(out, "--preserve-modtime    Preserve the original modification time in spite of edits\n");
706     fprintf(out, "--with-filename       Prefix each output line with the FLAC file name\n");
707     fprintf(out, "                      (the default if more than one FLAC file is specified)\n");
708     fprintf(out, "--no-filename         Do not prefix each output line with the FLAC file name\n");
709     fprintf(out, "                      (the default if only one FLAC file is specified)\n");
710     fprintf(out, "--no-utf8-convert     Do not convert Vorbis comments from UTF-8 to local charset,\n");
711     fprintf(out, "                      or vice versa.  This is useful for scripts.\n");
712     fprintf(out, "--dont-use-padding    By default metaflac tries to use padding where possible\n");
713     fprintf(out, "                      to avoid rewriting the entire file if the metadata size\n");
714     fprintf(out, "                      changes.  Use this option to tell metaflac to not take\n");
715     fprintf(out, "                      advantage of padding this way.\n");
716 }
717
718 int short_usage(const char *message, ...)
719 {
720         va_list args;
721
722         if(message) {
723                 va_start(args, message);
724
725                 (void) vfprintf(stderr, message, args);
726
727                 va_end(args);
728
729         }
730         usage_header(stderr);
731         fprintf(stderr, "\n");
732         fprintf(stderr, "This is the short help; for full help use 'metaflac --help'\n");
733         fprintf(stderr, "\n");
734         usage_summary(stderr);
735
736         return message? 1 : 0;
737 }
738
739 int long_usage(const char *message, ...)
740 {
741         FILE *out = (message? stderr : stdout);
742         va_list args;
743
744         if(message) {
745                 va_start(args, message);
746
747                 (void) vfprintf(stderr, message, args);
748
749                 va_end(args);
750
751         }
752         usage_header(out);
753     fprintf(out, "\n");
754         usage_summary(out);
755     fprintf(out, "\n");
756     fprintf(out, "Shorthand operations:\n");
757     fprintf(out, "--show-md5sum         Show the MD5 signature from the STREAMINFO block.\n");
758     fprintf(out, "--show-min-blocksize  Show the minimum block size from the STREAMINFO block.\n");
759     fprintf(out, "--show-max-blocksize  Show the maximum block size from the STREAMINFO block.\n");
760     fprintf(out, "--show-min-framesize  Show the minimum frame size from the STREAMINFO block.\n");
761     fprintf(out, "--show-max-framesize  Show the maximum frame size from the STREAMINFO block.\n");
762     fprintf(out, "--show-sample-rate    Show the sample rate from the STREAMINFO block.\n");
763     fprintf(out, "--show-channels       Show the number of channels from the STREAMINFO block.\n");
764     fprintf(out, "--show-bps            Show the # of bits per sample from the STREAMINFO block.\n");
765     fprintf(out, "--show-total-samples  Show the total # of samples from the STREAMINFO block.\n");
766     fprintf(out, "\n");
767     fprintf(out, "--show-vc-vendor      Show the vendor string from the VORBIS_COMMENT block.\n");
768     fprintf(out, "--show-vc-field=name  Show all Vorbis comment fields where the the field name\n");
769     fprintf(out, "                      matches 'name'.\n");
770     fprintf(out, "--remove-vc-field=name\n");
771     fprintf(out, "                      Remove all Vorbis comment fields whose field name is\n");
772     fprintf(out, "                      'name'.\n");
773     fprintf(out, "--remove-vc-firstfield=name\n");
774     fprintf(out, "                      Remove first Vorbis comment field whose field name is\n");
775     fprintf(out, "                      'name'.\n");
776     fprintf(out, "--remove-vc-all       Remove all Vorbis comment fields, leaving only the\n");
777     fprintf(out, "                      vendor string in the VORBIS_COMMENT block.\n");
778     fprintf(out, "--set-vc-field=field  Add a Vorbis comment field.  The field must comply with\n");
779     fprintf(out, "                      the Vorbis comment spec, of the form \"NAME=VALUE\".  If\n");
780     fprintf(out, "                      there is currently no VORBIS_COMMENT block, one will be\n");
781     fprintf(out, "                      created.\n");
782     fprintf(out, "--add-padding=length  Add a padding block of the given length (in bytes).\n");
783     fprintf(out, "                      The overall length of the new block will be 4 + length;\n");
784     fprintf(out, "                      the extra 4 bytes is for the metadata block header.\n");
785     fprintf(out, "\n");
786     fprintf(out, "Major operations:\n");
787     fprintf(out, "--list\n");
788     fprintf(out, "    List the contents of one or more metadata blocks to stdout.  By default,\n");
789     fprintf(out, "    all metadata blocks are listed in text format.  Use the following options\n");
790     fprintf(out, "    to change this behavior:\n");
791     fprintf(out, "\n");
792     fprintf(out, "    --block-number=#[,#[...]]\n");
793     fprintf(out, "    An optional comma-separated list of block numbers to display.  The first\n");
794     fprintf(out, "    block, the STREAMINFO block, is block 0.\n");
795     fprintf(out, "\n");
796     fprintf(out, "    --block-type=type[,type[...]]\n");
797     fprintf(out, "    --except-block-type=type[,type[...]]\n");
798     fprintf(out, "    An optional comma-separated list of block types to included or ignored\n");
799     fprintf(out, "    with this option.  Use only one of --block-type or --except-block-type.\n");
800     fprintf(out, "    The valid block types are: STREAMINFO, PADDING, APPLICATION, SEEKTABLE,\n");
801     fprintf(out, "    VORBIS_COMMENT.  You may narrow down the types of APPLICATION blocks\n");
802     fprintf(out, "    displayed as follows:\n");
803     fprintf(out, "        APPLICATION:abcd        The APPLICATION block(s) whose textual repre-\n");
804     fprintf(out, "                                sentation of the 4-byte ID is \"abcd\"\n");
805     fprintf(out, "        APPLICATION:0xXXXXXXXX  The APPLICATION block(s) whose hexadecimal big-\n");
806     fprintf(out, "                                endian representation of the 4-byte ID is\n");
807     fprintf(out, "                                \"0xXXXXXXXX\".  For the example \"abcd\" above the\n");
808     fprintf(out, "                                hexadecimal equivalalent is 0x61626364\n");
809     fprintf(out, "\n");
810     fprintf(out, "    NOTE: if both --block-number and --[except-]block-type are specified,\n");
811     fprintf(out, "          the result is the logical AND of both arguments.\n");
812     fprintf(out, "\n");
813 #if 0
814         /*@@@ not implemented yet */
815     fprintf(out, "    --data-format=binary|text\n");
816     fprintf(out, "    By default a human-readable text representation of the data is displayed.\n");
817     fprintf(out, "    You may specify --data-format=binary to dump the raw binary form of each\n");
818     fprintf(out, "    metadata block.  The output can be read in using a subsequent call to\n");
819     fprintf(out, "    "metaflac --append --from-file=..."\n");
820     fprintf(out, "\n");
821 #endif
822     fprintf(out, "    --application-data-format=hexdump|text\n");
823     fprintf(out, "    If the application block you are displaying contains binary data but your\n");
824     fprintf(out, "    --data-format=text, you can display a hex dump of the application data\n");
825     fprintf(out, "    contents instead using --application-data-format=hexdump\n");
826     fprintf(out, "\n");
827 #if 0
828         /*@@@ not implemented yet */
829     fprintf(out, "--append\n");
830     fprintf(out, "    Insert a metadata block from a file.  The input file must be in the same\n");
831     fprintf(out, "    format as generated with --list.\n");
832     fprintf(out, "\n");
833     fprintf(out, "    --block-number=#\n");
834     fprintf(out, "    Specify the insertion point (defaults to last block).  The new block will\n");
835     fprintf(out, "    be added after the given block number.  This prevents the illegal insertion\n");
836     fprintf(out, "    of a block before the first STREAMINFO block.  You may not --append another\n");
837     fprintf(out, "    STREAMINFO block.\n");
838     fprintf(out, "\n");
839     fprintf(out, "    --from-file=filename\n");
840     fprintf(out, "    Mandatory 'option' to specify the input file containing the block contents.\n");
841     fprintf(out, "\n");
842     fprintf(out, "    --data-format=binary|text\n");
843     fprintf(out, "    By default the block contents are assumed to be in binary format.  You can\n");
844     fprintf(out, "    override this by specifying --data-format=text\n");
845     fprintf(out, "\n");
846 #endif
847     fprintf(out, "--remove\n");
848     fprintf(out, "    Remove one or more metadata blocks from the metadata.  Unless\n");
849     fprintf(out, "    --dont-use-padding is specified, the blocks will be replaced with padding.\n");
850     fprintf(out, "    You may not remove the STREAMINFO block.\n");
851     fprintf(out, "\n");
852     fprintf(out, "    --block-number=#[,#[...]]\n");
853     fprintf(out, "    --block-type=type[,type[...]]\n");
854     fprintf(out, "    --except-block-type=type[,type[...]]\n");
855     fprintf(out, "    See --list above for usage.\n");
856     fprintf(out, "\n");
857     fprintf(out, "    NOTE: if both --block-number and --[except-]block-type are specified,\n");
858     fprintf(out, "          the result is the logical AND of both arguments.\n");
859     fprintf(out, "\n");
860     fprintf(out, "--remove-all\n");
861     fprintf(out, "    Remove all metadata blocks (except the STREAMINFO block) from the\n");
862     fprintf(out, "    metadata.  Unless --dont-use-padding is specified, the blocks will be\n");
863     fprintf(out, "    replaced with padding.\n");
864     fprintf(out, "\n");
865     fprintf(out, "--merge-padding\n");
866     fprintf(out, "    Merge adjacent PADDING blocks into single blocks.\n");
867     fprintf(out, "\n");
868     fprintf(out, "--sort-padding\n");
869     fprintf(out, "    Move all PADDING blocks to the end of the metadata and merge them into a\n");
870     fprintf(out, "    single block.\n");
871
872         return message? 1 : 0;
873 }
874
875 char *local_strdup(const char *source)
876 {
877         char *ret;
878         FLAC__ASSERT(0 != source);
879         if(0 == (ret = strdup(source)))
880                 die("out of memory during strdup()");
881         return ret;
882 }
883
884 FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation)
885 {
886         static const char * const violations[] = {
887                 "field name contains invalid character",
888                 "field contains no '=' character"
889         };
890
891         char *p, *q, *s;
892
893         if(0 != field)
894                 *field = local_strdup(field_ref);
895
896         s = local_strdup(field_ref);
897
898         if(0 == (p = strchr(s, '='))) {
899                 free(s);
900                 *violation = violations[1];
901                 return false;
902         }
903         *p++ = '\0';
904
905         for(q = s; *q; q++) {
906                 if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
907                         free(s);
908                         *violation = violations[0];
909                         return false;
910                 }
911         }
912
913         *name = local_strdup(s);
914         *value = local_strdup(p);
915         *length = strlen(p);
916
917         free(s);
918         return true;
919 }
920
921 static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation)
922 {
923         static const char * const violations[] = {
924                 "field name contains invalid character"
925         };
926
927         char *q, *s;
928
929         s = local_strdup(field_ref);
930
931         for(q = s; *q; q++) {
932                 if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
933                         free(s);
934                         *violation = violations[0];
935                         return false;
936                 }
937         }
938
939         *name = s;
940
941         return true;
942 }
943
944 FLAC__bool parse_add_padding(const char *in, unsigned *out)
945 {
946         *out = (unsigned)strtoul(in, 0, 10);
947         return *out < (1u << FLAC__STREAM_METADATA_LENGTH_LEN);
948 }
949
950 FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out)
951 {
952         char *p, *q, *s, *end;
953         long i;
954         unsigned entry;
955
956         if(*in == '\0')
957                 return false;
958
959         s = local_strdup(in);
960
961         /* first count the entries */
962         for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
963                 ;
964
965         /* make space */
966         FLAC__ASSERT(out->num_entries > 0);
967         if(0 == (out->entries = malloc(sizeof(unsigned) * out->num_entries)))
968                 die("out of memory allocating space for option list");
969
970         /* load 'em up */
971         entry = 0;
972         q = s;
973         while(q) {
974                 FLAC__ASSERT(entry < out->num_entries);
975                 if(0 != (p = strchr(q, ',')))
976                         *p++ = '\0';
977                 if(!isdigit((int)(*q)) || (i = strtol(q, &end, 10)) < 0 || *end) {
978                         free(s);
979                         return false;
980                 }
981                 out->entries[entry++] = (unsigned)i;
982                 q = p;
983         }
984         FLAC__ASSERT(entry == out->num_entries);
985
986         free(s);
987         return true;
988 }
989
990 FLAC__bool parse_block_type(const char *in, Argument_BlockType *out)
991 {
992         char *p, *q, *r, *s;
993         unsigned entry;
994
995         if(*in == '\0')
996                 return false;
997
998         s = local_strdup(in);
999
1000         /* first count the entries */
1001         for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
1002                 ;
1003
1004         /* make space */
1005         FLAC__ASSERT(out->num_entries > 0);
1006         if(0 == (out->entries = malloc(sizeof(Argument_BlockTypeEntry) * out->num_entries)))
1007                 die("out of memory allocating space for option list");
1008
1009         /* load 'em up */
1010         entry = 0;
1011         q = s;
1012         while(q) {
1013                 FLAC__ASSERT(entry < out->num_entries);
1014                 if(0 != (p = strchr(q, ',')))
1015                         *p++ = 0;
1016                 r = strchr(q, ':');
1017                 if(r)
1018                         *r++ = '\0';
1019                 if(0 != r && 0 != strcmp(q, "APPLICATION")) {
1020                         free(s);
1021                         return false;
1022                 }
1023                 if(0 == strcmp(q, "STREAMINFO")) {
1024                         out->entries[entry++].type = FLAC__METADATA_TYPE_STREAMINFO;
1025                 }
1026                 else if(0 == strcmp(q, "PADDING")) {
1027                         out->entries[entry++].type = FLAC__METADATA_TYPE_PADDING;
1028                 }
1029                 else if(0 == strcmp(q, "APPLICATION")) {
1030                         out->entries[entry].type = FLAC__METADATA_TYPE_APPLICATION;
1031                         out->entries[entry].filter_application_by_id = (0 != r);
1032                         if(0 != r) {
1033                                 if(strlen(r) == 4) {
1034                                         strcpy(out->entries[entry].application_id, r);
1035                                 }
1036                                 else if(strlen(r) == 10 && strncmp(r, "0x", 2) == 0 && strspn(r+2, "0123456789ABCDEFabcdef") == 8) {
1037                                         FLAC__uint32 x = strtoul(r+2, 0, 16);
1038                                         out->entries[entry].application_id[3] = (FLAC__byte)(x & 0xff);
1039                                         out->entries[entry].application_id[2] = (FLAC__byte)((x>>=8) & 0xff);
1040                                         out->entries[entry].application_id[1] = (FLAC__byte)((x>>=8) & 0xff);
1041                                         out->entries[entry].application_id[0] = (FLAC__byte)((x>>=8) & 0xff);
1042                                 }
1043                                 else {
1044                                         free(s);
1045                                         return false;
1046                                 }
1047                         }
1048                         entry++;
1049                 }
1050                 else if(0 == strcmp(q, "SEEKTABLE")) {
1051                         out->entries[entry++].type = FLAC__METADATA_TYPE_SEEKTABLE;
1052                 }
1053                 else if(0 == strcmp(q, "VORBIS_COMMENT")) {
1054                         out->entries[entry++].type = FLAC__METADATA_TYPE_VORBIS_COMMENT;
1055                 }
1056                 else {
1057                         free(s);
1058                         return false;
1059                 }
1060                 q = p;
1061         }
1062         FLAC__ASSERT(entry == out->num_entries);
1063
1064         free(s);
1065         return true;
1066 }
1067
1068 FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out)
1069 {
1070         if(0 == strcmp(in, "binary"))
1071                 out->is_binary = true;
1072         else if(0 == strcmp(in, "text"))
1073                 out->is_binary = false;
1074         else
1075                 return false;
1076         return true;
1077 }
1078
1079 FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out)
1080 {
1081         if(0 == strcmp(in, "hexdump"))
1082                 *out = true;
1083         else if(0 == strcmp(in, "text"))
1084                 *out = false;
1085         else
1086                 return false;
1087         return true;
1088 }
1089
1090 FLAC__bool do_operations(const CommandLineOptions *options)
1091 {
1092         FLAC__bool ok = true;
1093
1094         if(options->show_long_help) {
1095                 long_usage(0);
1096         }
1097         else if(options->args.checks.num_major_ops > 0) {
1098                 FLAC__ASSERT(options->args.checks.num_shorthand_ops == 0);
1099                 FLAC__ASSERT(options->args.checks.num_major_ops == 1);
1100                 FLAC__ASSERT(options->args.checks.num_major_ops == options->ops.num_operations);
1101                 ok = do_major_operation(options);
1102         }
1103         else if(options->args.checks.num_shorthand_ops > 0) {
1104                 FLAC__ASSERT(options->args.checks.num_shorthand_ops == options->ops.num_operations);
1105                 ok = do_shorthand_operations(options);
1106         }
1107
1108         return ok;
1109 }
1110
1111 FLAC__bool do_major_operation(const CommandLineOptions *options)
1112 {
1113         unsigned i;
1114         FLAC__bool ok = true;
1115
1116         /*@@@ to die after first error,  v---  add '&& ok' here */
1117         for(i = 0; i < options->num_files; i++)
1118                 ok &= do_major_operation_on_file(options->filenames[i], options);
1119
1120         return ok;
1121 }
1122
1123 FLAC__bool do_major_operation_on_file(const char *filename, const CommandLineOptions *options)
1124 {
1125         FLAC__bool ok = true, needs_write = false;
1126         FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
1127
1128         if(0 == chain)
1129                 die("out of memory allocating chain");
1130
1131         if(!FLAC__metadata_chain_read(chain, filename)) {
1132                 fprintf(stderr, "%s: ERROR: reading metadata, status = \"%s\"\n", filename, FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]);
1133                 return false;
1134         }
1135
1136         switch(options->ops.operations[0].type) {
1137                 case OP__LIST:
1138                         ok = do_major_operation__list(options->prefix_with_filename? filename : 0, chain, options);
1139                         break;
1140                 case OP__APPEND:
1141                         ok = do_major_operation__append(chain, options);
1142                         needs_write = true;
1143                         break;
1144                 case OP__REMOVE:
1145                         ok = do_major_operation__remove(chain, options);
1146                         needs_write = true;
1147                         break;
1148                 case OP__REMOVE_ALL:
1149                         ok = do_major_operation__remove_all(chain, options);
1150                         needs_write = true;
1151                         break;
1152                 case OP__MERGE_PADDING:
1153                         FLAC__metadata_chain_merge_padding(chain);
1154                         needs_write = true;
1155                         break;
1156                 case OP__SORT_PADDING:
1157                         FLAC__metadata_chain_sort_padding(chain);
1158                         needs_write = true;
1159                         break;
1160                 default:
1161                         FLAC__ASSERT(0);
1162                         return false;
1163         }
1164
1165         if(ok && needs_write) {
1166                 if(options->use_padding)
1167                         FLAC__metadata_chain_sort_padding(chain);
1168                 ok = FLAC__metadata_chain_write(chain, options->use_padding, options->preserve_modtime);
1169                 if(!ok)
1170                         fprintf(stderr, "%s: ERROR: writing FLAC file, error = %s\n", filename, FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]);
1171         }
1172
1173         FLAC__metadata_chain_delete(chain);
1174
1175         return ok;
1176 }
1177
1178 FLAC__bool do_major_operation__list(const char *filename, FLAC__Metadata_Chain *chain, const CommandLineOptions *options)
1179 {
1180         FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
1181         FLAC__StreamMetadata *block;
1182         FLAC__bool ok = true;
1183         unsigned block_number;
1184
1185         if(0 == iterator)
1186                 die("out of memory allocating iterator");
1187
1188         FLAC__metadata_iterator_init(iterator, chain);
1189
1190         block_number = 0;
1191         do {
1192                 block = FLAC__metadata_iterator_get_block(iterator);
1193                 ok &= (0 != block);
1194                 if(!ok)
1195                         fprintf(stderr, "%s: ERROR: couldn't get block from chain\n", filename);
1196                 else if(passes_filter(options, FLAC__metadata_iterator_get_block(iterator), block_number))
1197                         write_metadata(filename, block, block_number, options->application_data_format_is_hexdump);
1198                 block_number++;
1199         } while(ok && FLAC__metadata_iterator_next(iterator));
1200
1201         FLAC__metadata_iterator_delete(iterator);
1202
1203         return ok;
1204 }
1205
1206 FLAC__bool do_major_operation__append(FLAC__Metadata_Chain *chain, const CommandLineOptions *options)
1207 {
1208         (void) chain, (void) options;
1209         fprintf(stderr, "ERROR: --append not implemented yet\n"); /*@@@*/
1210         return false;
1211 }
1212
1213 FLAC__bool do_major_operation__remove(FLAC__Metadata_Chain *chain, const CommandLineOptions *options)
1214 {
1215         FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
1216         FLAC__bool ok = true;
1217         unsigned block_number;
1218
1219         if(0 == iterator)
1220                 die("out of memory allocating iterator");
1221
1222         FLAC__metadata_iterator_init(iterator, chain);
1223
1224         block_number = 0;
1225         while(ok && FLAC__metadata_iterator_next(iterator)) {
1226                 block_number++;
1227                 if(passes_filter(options, FLAC__metadata_iterator_get_block(iterator), block_number)) {
1228                         ok &= FLAC__metadata_iterator_delete_block(iterator, options->use_padding);
1229                         if(options->use_padding)
1230                                 ok &= FLAC__metadata_iterator_next(iterator);
1231                 }
1232         }
1233
1234         FLAC__metadata_iterator_delete(iterator);
1235
1236         return ok;
1237 }
1238
1239 FLAC__bool do_major_operation__remove_all(FLAC__Metadata_Chain *chain, const CommandLineOptions *options)
1240 {
1241         FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
1242         FLAC__bool ok = true;
1243
1244         if(0 == iterator)
1245                 die("out of memory allocating iterator");
1246
1247         FLAC__metadata_iterator_init(iterator, chain);
1248
1249         while(ok && FLAC__metadata_iterator_next(iterator)) {
1250                 ok &= FLAC__metadata_iterator_delete_block(iterator, options->use_padding);
1251                 if(options->use_padding)
1252                         ok &= FLAC__metadata_iterator_next(iterator);
1253         }
1254
1255         FLAC__metadata_iterator_delete(iterator);
1256
1257         return ok;
1258 }
1259
1260 FLAC__bool do_shorthand_operations(const CommandLineOptions *options)
1261 {
1262         unsigned i;
1263         FLAC__bool ok = true;
1264
1265         /*@@@ to die after first error,  v---  add '&& ok' here */
1266         for(i = 0; i < options->num_files; i++)
1267                 ok &= do_shorthand_operations_on_file(options->filenames[i], options);
1268
1269         return ok;
1270 }
1271
1272 FLAC__bool do_shorthand_operations_on_file(const char *filename, const CommandLineOptions *options)
1273 {
1274         unsigned i;
1275         FLAC__bool ok = true, needs_write = false;
1276         FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
1277
1278         if(0 == chain)
1279                 die("out of memory allocating chain");
1280
1281         if(!FLAC__metadata_chain_read(chain, filename)) {
1282                 fprintf(stderr, "%s: ERROR: reading metadata, status = \"%s\"\n", filename, FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]);
1283                 return false;
1284         }
1285
1286         for(i = 0; i < options->ops.num_operations && ok; i++)
1287                 ok &= do_shorthand_operation(options->prefix_with_filename? filename : 0, chain, &options->ops.operations[i], &needs_write, options->utf8_convert);
1288
1289         if(ok) {
1290                 if(options->use_padding)
1291                         FLAC__metadata_chain_sort_padding(chain);
1292                 ok = FLAC__metadata_chain_write(chain, options->use_padding, options->preserve_modtime);
1293                 if(!ok)
1294                         fprintf(stderr, "%s: ERROR: writing FLAC file, error = %s\n", filename, FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]);
1295         }
1296
1297         FLAC__metadata_chain_delete(chain);
1298
1299         return ok;
1300 }
1301
1302 FLAC__bool do_shorthand_operation(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool utf8_convert)
1303 {
1304         FLAC__bool ok = true;
1305
1306         switch(operation->type) {
1307                 case OP__SHOW_MD5SUM:
1308                 case OP__SHOW_MIN_BLOCKSIZE:
1309                 case OP__SHOW_MAX_BLOCKSIZE:
1310                 case OP__SHOW_MIN_FRAMESIZE:
1311                 case OP__SHOW_MAX_FRAMESIZE:
1312                 case OP__SHOW_SAMPLE_RATE:
1313                 case OP__SHOW_CHANNELS:
1314                 case OP__SHOW_BPS:
1315                 case OP__SHOW_TOTAL_SAMPLES:
1316                         ok = do_shorthand_operation__streaminfo(filename, chain, operation->type);
1317                         break;
1318                 case OP__SHOW_VC_VENDOR:
1319                 case OP__SHOW_VC_FIELD:
1320                 case OP__REMOVE_VC_ALL:
1321                 case OP__REMOVE_VC_FIELD:
1322                 case OP__REMOVE_VC_FIRSTFIELD:
1323                 case OP__SET_VC_FIELD:
1324                         ok = do_shorthand_operation__vorbis_comment(filename, chain, operation, needs_write, !utf8_convert);
1325                         break;
1326                 case OP__ADD_PADDING:
1327                         ok = do_shorthand_operation__add_padding(filename, chain, operation->argument.add_padding.length, needs_write);
1328                         break;
1329                 default:
1330                         ok = false;
1331                         FLAC__ASSERT(0);
1332                         break;
1333         };
1334
1335         return ok;
1336 }
1337
1338 FLAC__bool do_shorthand_operation__add_padding(const char *filename, FLAC__Metadata_Chain *chain, unsigned length, FLAC__bool *needs_write)
1339 {
1340         FLAC__StreamMetadata *padding = 0;
1341         FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
1342
1343         if(0 == iterator)
1344                 die("out of memory allocating iterator");
1345
1346         FLAC__metadata_iterator_init(iterator, chain);
1347
1348         while(FLAC__metadata_iterator_next(iterator))
1349                 ;
1350
1351         padding = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING);
1352         if(0 == padding)
1353                 die("out of memory allocating PADDING block");
1354
1355         padding->length = length;
1356
1357         if(!FLAC__metadata_iterator_insert_block_after(iterator, padding)) {
1358                 fprintf(stderr, "%s: ERROR: adding new PADDING block to metadata, status =\"%s\"\n", filename, FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]);
1359                 FLAC__metadata_object_delete(padding);
1360                 return false;
1361         }
1362
1363         *needs_write = true;
1364         return true;
1365 }
1366
1367 FLAC__bool do_shorthand_operation__streaminfo(const char *filename, FLAC__Metadata_Chain *chain, OperationType op)
1368 {
1369         unsigned i;
1370         FLAC__bool ok = true;
1371         FLAC__StreamMetadata *block;
1372         FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
1373
1374         if(0 == iterator)
1375                 die("out of memory allocating iterator");
1376
1377         FLAC__metadata_iterator_init(iterator, chain);
1378
1379         block = FLAC__metadata_iterator_get_block(iterator);
1380
1381         FLAC__ASSERT(0 != block);
1382         FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_STREAMINFO);
1383
1384         if(0 != filename)
1385                 printf("%s:", filename);
1386
1387         switch(op) {
1388                 case OP__SHOW_MD5SUM:
1389                         for(i = 0; i < 16; i++)
1390                                 printf("%02x", block->data.stream_info.md5sum[i]);
1391                         printf("\n");
1392                         break;
1393                 case OP__SHOW_MIN_BLOCKSIZE:
1394                         printf("%u\n", block->data.stream_info.min_blocksize);
1395                         break;
1396                 case OP__SHOW_MAX_BLOCKSIZE:
1397                         printf("%u\n", block->data.stream_info.max_blocksize);
1398                         break;
1399                 case OP__SHOW_MIN_FRAMESIZE:
1400                         printf("%u\n", block->data.stream_info.min_framesize);
1401                         break;
1402                 case OP__SHOW_MAX_FRAMESIZE:
1403                         printf("%u\n", block->data.stream_info.max_framesize);
1404                         break;
1405                 case OP__SHOW_SAMPLE_RATE:
1406                         printf("%u\n", block->data.stream_info.sample_rate);
1407                         break;
1408                 case OP__SHOW_CHANNELS:
1409                         printf("%u\n", block->data.stream_info.channels);
1410                         break;
1411                 case OP__SHOW_BPS:
1412                         printf("%u\n", block->data.stream_info.bits_per_sample);
1413                         break;
1414                 case OP__SHOW_TOTAL_SAMPLES:
1415                         printf("%llu\n", block->data.stream_info.total_samples);
1416                         break;
1417                 default:
1418                         ok = false;
1419                         FLAC__ASSERT(0);
1420                         break;
1421         };
1422
1423         FLAC__metadata_iterator_delete(iterator);
1424
1425         return ok;
1426 }
1427
1428 FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool raw)
1429 {
1430         FLAC__bool ok = true, found_vc_block = false;
1431         FLAC__StreamMetadata *block = 0;
1432         FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
1433
1434         if(0 == iterator)
1435                 die("out of memory allocating iterator");
1436
1437         FLAC__metadata_iterator_init(iterator, chain);
1438
1439         do {
1440                 block = FLAC__metadata_iterator_get_block(iterator);
1441                 if(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
1442                         found_vc_block = true;
1443         } while(!found_vc_block && FLAC__metadata_iterator_next(iterator));
1444
1445         /* create a new block if necessary */
1446         if(!found_vc_block && operation->type == OP__SET_VC_FIELD) {
1447                 block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
1448                 if(0 == block)
1449                         die("out of memory allocating VORBIS_COMMENT block");
1450                 while(FLAC__metadata_iterator_next(iterator))
1451                         ;
1452                 if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) {
1453                         fprintf(stderr, "%s: ERROR: adding new VORBIS_COMMENT block to metadata, status =\"%s\"\n", filename, FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]);
1454                         return false;
1455                 }
1456                 /* iterator is left pointing to new block */
1457                 FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block);
1458         }
1459
1460         FLAC__ASSERT(0 != block);
1461         FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
1462
1463         switch(operation->type) {
1464                 case OP__SHOW_VC_VENDOR:
1465                         write_vc_field(filename, &block->data.vorbis_comment.vendor_string, raw);
1466                         break;
1467                 case OP__SHOW_VC_FIELD:
1468                         write_vc_fields(filename, operation->argument.show_vc_field.field_name, block->data.vorbis_comment.comments, block->data.vorbis_comment.num_comments, raw);
1469                         break;
1470                 case OP__REMOVE_VC_ALL:
1471                         ok = remove_vc_all(filename, block, needs_write);
1472                         break;
1473                 case OP__REMOVE_VC_FIELD:
1474                         ok = remove_vc_field(block, operation->argument.remove_vc_field.field_name, needs_write);
1475                         break;
1476                 case OP__REMOVE_VC_FIRSTFIELD:
1477                         ok = remove_vc_firstfield(filename, block, operation->argument.remove_vc_firstfield.field_name, needs_write);
1478                         break;
1479                 case OP__SET_VC_FIELD:
1480                         ok = set_vc_field(filename, block, &operation->argument.set_vc_field, needs_write, raw);
1481                         break;
1482                 default:
1483                         ok = false;
1484                         FLAC__ASSERT(0);
1485                         break;
1486         };
1487
1488         return ok;
1489 }
1490
1491 FLAC__bool passes_filter(const CommandLineOptions *options, const FLAC__StreamMetadata *block, unsigned block_number)
1492 {
1493         unsigned i, j;
1494         FLAC__bool matches_number = false, matches_type = false;
1495         FLAC__bool has_block_number_arg = false;
1496
1497         for(i = 0; i < options->args.num_arguments; i++) {
1498                 if(options->args.arguments[i].type == ARG__BLOCK_TYPE || options->args.arguments[i].type == ARG__EXCEPT_BLOCK_TYPE) {
1499                         for(j = 0; j < options->args.arguments[i].value.block_type.num_entries; j++) {
1500                                 if(options->args.arguments[i].value.block_type.entries[j].type == block->type) {
1501                                         if(block->type != FLAC__METADATA_TYPE_APPLICATION || !options->args.arguments[i].value.block_type.entries[j].filter_application_by_id || 0 == memcmp(options->args.arguments[i].value.block_type.entries[j].application_id, block->data.application.id, FLAC__STREAM_METADATA_APPLICATION_ID_LEN/8))
1502                                                 matches_type = true;
1503                                 }
1504                         }
1505                 }
1506                 else if(options->args.arguments[i].type == ARG__BLOCK_NUMBER) {
1507                         has_block_number_arg = true;
1508                         for(j = 0; j < options->args.arguments[i].value.block_number.num_entries; j++) {
1509                                 if(options->args.arguments[i].value.block_number.entries[j] == block_number)
1510                                         matches_number = true;
1511                         }
1512                 }
1513         }
1514
1515         if(!has_block_number_arg)
1516                 matches_number = true;
1517
1518         if(options->args.checks.has_block_type) {
1519                 FLAC__ASSERT(!options->args.checks.has_except_block_type);
1520         }
1521         else if(options->args.checks.has_except_block_type)
1522                 matches_type = !matches_type;
1523         else
1524                 matches_type = true;
1525
1526         return matches_number && matches_type;
1527 }
1528
1529 void write_metadata(const char *filename, FLAC__StreamMetadata *block, unsigned block_number, FLAC__bool hexdump_application)
1530 {
1531         unsigned i;
1532
1533 /*@@@ yuck, should do this with a varargs function or something: */
1534 #define PPR if(filename)printf("%s:",filename);
1535         PPR; printf("METADATA block #%u\n", block_number);
1536         PPR; printf("  type: %u (%s)\n", (unsigned)block->type, block->type<=FLAC__METADATA_TYPE_VORBIS_COMMENT? FLAC__MetadataTypeString[block->type] : "UNKNOWN");
1537         PPR; printf("  is last: %s\n", block->is_last? "true":"false");
1538         PPR; printf("  length: %u\n", block->length);
1539
1540         switch(block->type) {
1541                 case FLAC__METADATA_TYPE_STREAMINFO:
1542                         PPR; printf("  minumum blocksize: %u samples\n", block->data.stream_info.min_blocksize);
1543                         PPR; printf("  maximum blocksize: %u samples\n", block->data.stream_info.max_blocksize);
1544                         PPR; printf("  minimum framesize: %u bytes\n", block->data.stream_info.min_framesize);
1545                         PPR; printf("  maximum framesize: %u bytes\n", block->data.stream_info.max_framesize);
1546                         PPR; printf("  sample_rate: %u Hz\n", block->data.stream_info.sample_rate);
1547                         PPR; printf("  channels: %u\n", block->data.stream_info.channels);
1548                         PPR; printf("  bits-per-sample: %u\n", block->data.stream_info.bits_per_sample);
1549                         PPR; printf("  total samples: %llu\n", block->data.stream_info.total_samples);
1550                         PPR; printf("  MD5 signature: ");
1551                         for(i = 0; i < 16; i++) {
1552                                 printf("%02x", (unsigned)block->data.stream_info.md5sum[i]);
1553                         }
1554                         printf("\n");
1555                         break;
1556                 case FLAC__METADATA_TYPE_PADDING:
1557                         /* nothing to print */
1558                         break;
1559                 case FLAC__METADATA_TYPE_APPLICATION:
1560                         PPR; printf("  application ID: ");
1561                         for(i = 0; i < 4; i++) {
1562                                 PPR; printf("%02x", block->data.application.id[i]);
1563                         }
1564                         PPR; printf("\n");
1565                         PPR; printf("  data contents:\n");
1566                         if(0 != block->data.application.data) {
1567                                 if(hexdump_application)
1568                                         hexdump(filename, block->data.application.data, block->length - FLAC__STREAM_METADATA_HEADER_LENGTH, "    ");
1569                                 else
1570                                         (void) fwrite(block->data.application.data, 1, block->length - FLAC__STREAM_METADATA_HEADER_LENGTH, stdout);
1571                         }
1572                         break;
1573                 case FLAC__METADATA_TYPE_SEEKTABLE:
1574                         PPR; printf("  seek points: %u\n", block->data.seek_table.num_points);
1575                         for(i = 0; i < block->data.seek_table.num_points; i++) {
1576                                 if(block->data.seek_table.points[i].sample_number != FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER) {
1577                                         PPR; printf("    point %d: sample_number=%llu, stream_offset=%llu, frame_samples=%u\n", i, block->data.seek_table.points[i].sample_number, block->data.seek_table.points[i].stream_offset, block->data.seek_table.points[i].frame_samples);
1578                                 }
1579                                 else {
1580                                         PPR; printf("    point %d: PLACEHOLDER\n", i);
1581                                 }
1582                         }
1583                         break;
1584                 case FLAC__METADATA_TYPE_VORBIS_COMMENT:
1585                         PPR; printf("  vendor string: ");
1586                         if(0 != block->data.vorbis_comment.vendor_string.entry)
1587                                 fwrite(block->data.vorbis_comment.vendor_string.entry, 1, block->data.vorbis_comment.vendor_string.length, stdout);
1588                         printf("\n");
1589                         PPR; printf("  comments: %u\n", block->data.vorbis_comment.num_comments);
1590                         for(i = 0; i < block->data.vorbis_comment.num_comments; i++) {
1591                                 PPR; printf("    comment[%u]: ", i);
1592                                 fwrite(block->data.vorbis_comment.comments[i].entry, 1, block->data.vorbis_comment.comments[i].length, stdout);
1593                                 printf("\n");
1594                         }
1595                         break;
1596                 default:
1597                         PPR; printf("SKIPPING block of unknown type\n");
1598                         break;
1599         }
1600 #undef PPR
1601 }
1602
1603 void write_vc_field(const char *filename, const FLAC__StreamMetadata_VorbisComment_Entry *entry, FLAC__bool raw)
1604 {
1605         if(0 != entry->entry) {
1606                 char *converted;
1607
1608                 if(filename)
1609                         printf("%s:", filename);
1610
1611                 if(!raw && utf8_decode(entry->entry, &converted) >= 0) {
1612                         (void) fwrite(converted, 1, strlen(converted), stdout);
1613                         free(converted);
1614                 }
1615                 else {
1616                         (void) fwrite(entry->entry, 1, entry->length, stdout);
1617                 }
1618         }
1619
1620         printf("\n");
1621 }
1622
1623 void write_vc_fields(const char *filename, const char *field_name, const FLAC__StreamMetadata_VorbisComment_Entry entry[], unsigned num_entries, FLAC__bool raw)
1624 {
1625         unsigned i;
1626         const unsigned field_name_length = strlen(field_name);
1627
1628         for(i = 0; i < num_entries; i++) {
1629                 if(field_name_matches_entry(field_name, field_name_length, entry + i))
1630                         write_vc_field(filename, entry + i, raw);
1631         }
1632 }
1633
1634 FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write)
1635 {
1636         FLAC__ASSERT(0 != block);
1637         FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
1638         FLAC__ASSERT(0 != needs_write);
1639
1640         if(0 != block->data.vorbis_comment.comments) {
1641                 FLAC__ASSERT(block->data.vorbis_comment.num_comments > 0);
1642                 if(!FLAC__metadata_object_vorbiscomment_resize_comments(block, 0)) {
1643                         fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
1644                         return false;
1645                 }
1646                 *needs_write = true;
1647         }
1648         else {
1649                 FLAC__ASSERT(block->data.vorbis_comment.num_comments == 0);
1650         }
1651
1652         return true;
1653 }
1654
1655 FLAC__bool remove_vc_field(FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write)
1656 {
1657         FLAC__bool ok = true;
1658         const unsigned field_name_length = strlen(field_name);
1659         int i;
1660
1661         FLAC__ASSERT(0 != block);
1662         FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
1663         FLAC__ASSERT(0 != needs_write);
1664
1665         /* must delete from end to start otherwise it will interfere with our iteration */
1666         for(i = (int)block->data.vorbis_comment.num_comments - 1; ok && i >= 0; i--) {
1667                 if(field_name_matches_entry(field_name, field_name_length, block->data.vorbis_comment.comments + i)) {
1668                         ok &= FLAC__metadata_object_vorbiscomment_delete_comment(block, (unsigned)i);
1669                         if(ok)
1670                                 *needs_write = true;
1671                 }
1672         }
1673
1674         return ok;
1675 }
1676
1677 FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write)
1678 {
1679         const unsigned field_name_length = strlen(field_name);
1680         unsigned i;
1681
1682         FLAC__ASSERT(0 != block);
1683         FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
1684         FLAC__ASSERT(0 != needs_write);
1685
1686         for(i = 0; i < block->data.vorbis_comment.num_comments; i++) {
1687                 if(field_name_matches_entry(field_name, field_name_length, block->data.vorbis_comment.comments + i)) {
1688                         if(!FLAC__metadata_object_vorbiscomment_delete_comment(block, (unsigned)i)) {
1689                                 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
1690                                 return false;
1691                         }
1692                         else
1693                                 *needs_write = true;
1694                         break;
1695                 }
1696         }
1697
1698         return true;
1699 }
1700
1701 FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw)
1702 {
1703         FLAC__StreamMetadata_VorbisComment_Entry entry;
1704         char *converted;
1705         FLAC__bool needs_free = false;
1706
1707         FLAC__ASSERT(0 != block);
1708         FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
1709         FLAC__ASSERT(0 != field);
1710         FLAC__ASSERT(0 != needs_write);
1711
1712         if(raw) {
1713                 entry.entry = field->field;
1714         }
1715         else if(utf8_encode(field->field, &converted) >= 0) {
1716                 entry.entry = converted;
1717                 needs_free = true;
1718         }
1719         else {
1720                 fprintf(stderr, "%s: ERROR: couldn't convert comment to UTF-8\n", filename);
1721                 return false;
1722         }
1723
1724         entry.length = strlen(entry.entry);
1725
1726         if(!FLAC__metadata_object_vorbiscomment_insert_comment(block, block->data.vorbis_comment.num_comments, entry, /*copy=*/true)) {
1727                 if(needs_free)
1728                         free(converted);
1729                 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
1730                 return false;
1731         }
1732         else {
1733                 *needs_write = true;
1734                 if(needs_free)
1735                         free(converted);
1736                 return true;
1737         }
1738 }
1739
1740 FLAC__bool field_name_matches_entry(const char *field_name, unsigned field_name_length, const FLAC__StreamMetadata_VorbisComment_Entry *entry)
1741 {
1742         return (0 != memchr(entry->entry, '=', entry->length) && 0 == strncmp(field_name, entry->entry, field_name_length));
1743 }
1744
1745 void hexdump(const char *filename, const FLAC__byte *buf, unsigned bytes, const char *indent)
1746 {
1747         unsigned i, left = bytes;
1748         const FLAC__byte *b = buf;
1749
1750         for(i = 0; i < bytes; i += 16) {
1751                 printf("%s%s%s%08X: "
1752                         "%02X %02X %02X %02X %02X %02X %02X %02X "
1753                         "%02X %02X %02X %02X %02X %02X %02X %02X "
1754                         "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n",
1755                         filename? filename:"", filename? ":":"",
1756                         indent, i,
1757                         left >  0? (unsigned char)b[ 0] : 0,
1758                         left >  1? (unsigned char)b[ 1] : 0,
1759                         left >  2? (unsigned char)b[ 2] : 0,
1760                         left >  3? (unsigned char)b[ 3] : 0,
1761                         left >  4? (unsigned char)b[ 4] : 0,
1762                         left >  5? (unsigned char)b[ 5] : 0,
1763                         left >  6? (unsigned char)b[ 6] : 0,
1764                         left >  7? (unsigned char)b[ 7] : 0,
1765                         left >  8? (unsigned char)b[ 8] : 0,
1766                         left >  9? (unsigned char)b[ 9] : 0,
1767                         left > 10? (unsigned char)b[10] : 0,
1768                         left > 11? (unsigned char)b[11] : 0,
1769                         left > 12? (unsigned char)b[12] : 0,
1770                         left > 13? (unsigned char)b[13] : 0,
1771                         left > 14? (unsigned char)b[14] : 0,
1772                         left > 15? (unsigned char)b[15] : 0,
1773                         (left >  0) ? (isprint(b[ 0]) ? b[ 0] : '.') : ' ',
1774                         (left >  1) ? (isprint(b[ 1]) ? b[ 1] : '.') : ' ',
1775                         (left >  2) ? (isprint(b[ 2]) ? b[ 2] : '.') : ' ',
1776                         (left >  3) ? (isprint(b[ 3]) ? b[ 3] : '.') : ' ',
1777                         (left >  4) ? (isprint(b[ 4]) ? b[ 4] : '.') : ' ',
1778                         (left >  5) ? (isprint(b[ 5]) ? b[ 5] : '.') : ' ',
1779                         (left >  6) ? (isprint(b[ 6]) ? b[ 6] : '.') : ' ',
1780                         (left >  7) ? (isprint(b[ 7]) ? b[ 7] : '.') : ' ',
1781                         (left >  8) ? (isprint(b[ 8]) ? b[ 8] : '.') : ' ',
1782                         (left >  9) ? (isprint(b[ 9]) ? b[ 9] : '.') : ' ',
1783                         (left > 10) ? (isprint(b[10]) ? b[10] : '.') : ' ',
1784                         (left > 11) ? (isprint(b[11]) ? b[11] : '.') : ' ',
1785                         (left > 12) ? (isprint(b[12]) ? b[12] : '.') : ' ',
1786                         (left > 13) ? (isprint(b[13]) ? b[13] : '.') : ' ',
1787                         (left > 14) ? (isprint(b[14]) ? b[14] : '.') : ' ',
1788                         (left > 15) ? (isprint(b[15]) ? b[15] : '.') : ' '
1789                 );
1790                 left -= 16;
1791                 b += 16;
1792    }
1793 }