1 /* metaflac - Command-line FLAC metadata editor
2 * Copyright (C) 2001,2002,2003,2004,2005,2006,2007,2008,2009 Josh Coalson
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.
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.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "FLAC/assert.h"
27 #include "share/alloc.h"
28 #include "share/compat.h"
29 #include "share/grabbag/replaygain.h"
36 share__getopt format struct; note we don't use short options so we just
37 set the 'val' field to 0 everywhere to indicate a valid option.
39 struct share__option long_options_
[] = {
41 { "preserve-modtime", 0, 0, 0 },
42 { "with-filename", 0, 0, 0 },
43 { "no-filename", 0, 0, 0 },
44 { "no-utf8-convert", 0, 0, 0 },
45 { "dont-use-padding", 0, 0, 0 },
46 { "no-cued-seekpoints", 0, 0, 0 },
47 /* shorthand operations */
48 { "show-md5sum", 0, 0, 0 },
49 { "show-min-blocksize", 0, 0, 0 },
50 { "show-max-blocksize", 0, 0, 0 },
51 { "show-min-framesize", 0, 0, 0 },
52 { "show-max-framesize", 0, 0, 0 },
53 { "show-sample-rate", 0, 0, 0 },
54 { "show-channels", 0, 0, 0 },
55 { "show-bps", 0, 0, 0 },
56 { "show-total-samples", 0, 0, 0 },
57 { "set-md5sum", 1, 0, 0 }, /* undocumented */
58 { "set-min-blocksize", 1, 0, 0 }, /* undocumented */
59 { "set-max-blocksize", 1, 0, 0 }, /* undocumented */
60 { "set-min-framesize", 1, 0, 0 }, /* undocumented */
61 { "set-max-framesize", 1, 0, 0 }, /* undocumented */
62 { "set-sample-rate", 1, 0, 0 }, /* undocumented */
63 { "set-channels", 1, 0, 0 }, /* undocumented */
64 { "set-bps", 1, 0, 0 }, /* undocumented */
65 { "set-total-samples", 1, 0, 0 }, /* undocumented */ /* WATCHOUT: used by test/test_flac.sh on windows */
66 { "show-vendor-tag", 0, 0, 0 },
67 { "show-tag", 1, 0, 0 },
68 { "remove-all-tags", 0, 0, 0 },
69 { "remove-tag", 1, 0, 0 },
70 { "remove-first-tag", 1, 0, 0 },
71 { "set-tag", 1, 0, 0 },
72 { "set-tag-from-file", 1, 0, 0 },
73 { "import-tags-from", 1, 0, 0 },
74 { "export-tags-to", 1, 0, 0 },
75 { "import-cuesheet-from", 1, 0, 0 },
76 { "export-cuesheet-to", 1, 0, 0 },
77 { "import-picture-from", 1, 0, 0 },
78 { "export-picture-to", 1, 0, 0 },
79 { "add-seekpoint", 1, 0, 0 },
80 { "add-replay-gain", 0, 0, 0 },
81 { "remove-replay-gain", 0, 0, 0 },
82 { "add-padding", 1, 0, 0 },
83 /* major operations */
85 { "version", 0, 0, 0 },
87 { "append", 0, 0, 0 },
88 { "remove", 0, 0, 0 },
89 { "remove-all", 0, 0, 0 },
90 { "merge-padding", 0, 0, 0 },
91 { "sort-padding", 0, 0, 0 },
92 /* major operation arguments */
93 { "block-number", 1, 0, 0 },
94 { "block-type", 1, 0, 0 },
95 { "except-block-type", 1, 0, 0 },
96 { "data-format", 1, 0, 0 },
97 { "application-data-format", 1, 0, 0 },
98 { "from-file", 1, 0, 0 },
102 static FLAC__bool
parse_option(int option_index
, const char *option_argument
, CommandLineOptions
*options
);
103 static void append_new_operation(CommandLineOptions
*options
, Operation operation
);
104 static void append_new_argument(CommandLineOptions
*options
, Argument argument
);
105 static Operation
*append_major_operation(CommandLineOptions
*options
, OperationType type
);
106 static Operation
*append_shorthand_operation(CommandLineOptions
*options
, OperationType type
);
107 static Argument
*find_argument(CommandLineOptions
*options
, ArgumentType type
);
108 static Operation
*find_shorthand_operation(CommandLineOptions
*options
, OperationType type
);
109 static Argument
*append_argument(CommandLineOptions
*options
, ArgumentType type
);
110 static FLAC__bool
parse_md5(const char *src
, FLAC__byte dest
[16]);
111 static FLAC__bool
parse_uint32(const char *src
, FLAC__uint32
*dest
);
112 static FLAC__bool
parse_uint64(const char *src
, FLAC__uint64
*dest
);
113 static FLAC__bool
parse_string(const char *src
, char **dest
);
114 static FLAC__bool
parse_vorbis_comment_field_name(const char *field_ref
, char **name
, const char **violation
);
115 static FLAC__bool
parse_add_seekpoint(const char *in
, char **out
, const char **violation
);
116 static FLAC__bool
parse_add_padding(const char *in
, unsigned *out
);
117 static FLAC__bool
parse_block_number(const char *in
, Argument_BlockNumber
*out
);
118 static FLAC__bool
parse_block_type(const char *in
, Argument_BlockType
*out
);
119 static FLAC__bool
parse_data_format(const char *in
, Argument_DataFormat
*out
);
120 static FLAC__bool
parse_application_data_format(const char *in
, FLAC__bool
*out
);
121 static void undocumented_warning(const char *opt
);
124 void init_options(CommandLineOptions
*options
)
126 options
->preserve_modtime
= false;
128 /* '2' is a hack to mean "use default if not forced on command line" */
129 FLAC__ASSERT(true != 2);
130 options
->prefix_with_filename
= 2;
132 options
->utf8_convert
= true;
133 options
->use_padding
= true;
134 options
->cued_seekpoints
= true;
135 options
->show_long_help
= false;
136 options
->show_version
= false;
137 options
->application_data_format_is_hexdump
= false;
139 options
->ops
.operations
= 0;
140 options
->ops
.num_operations
= 0;
141 options
->ops
.capacity
= 0;
143 options
->args
.arguments
= 0;
144 options
->args
.num_arguments
= 0;
145 options
->args
.capacity
= 0;
147 options
->args
.checks
.num_shorthand_ops
= 0;
148 options
->args
.checks
.num_major_ops
= 0;
149 options
->args
.checks
.has_block_type
= false;
150 options
->args
.checks
.has_except_block_type
= false;
152 options
->num_files
= 0;
153 options
->filenames
= 0;
156 FLAC__bool
parse_options(int argc
, char *argv
[], CommandLineOptions
*options
)
159 int option_index
= 1;
160 FLAC__bool had_error
= false;
162 while ((ret
= share__getopt_long(argc
, argv
, "", long_options_
, &option_index
)) != -1) {
165 had_error
|= !parse_option(option_index
, share__optarg
, options
);
177 if(options
->prefix_with_filename
== 2)
178 options
->prefix_with_filename
= (argc
- share__optind
> 1);
180 if(share__optind
>= argc
&& !options
->show_long_help
&& !options
->show_version
) {
181 flac_fprintf(stderr
,"ERROR: you must specify at least one FLAC file;\n");
182 flac_fprintf(stderr
," metaflac cannot be used as a pipe\n");
186 options
->num_files
= argc
- share__optind
;
188 if(options
->num_files
> 0) {
190 if(0 == (options
->filenames
= safe_malloc_mul_2op_(sizeof(char*), /*times*/options
->num_files
)))
191 die("out of memory allocating space for file names list");
192 while(share__optind
< argc
)
193 options
->filenames
[i
++] = local_strdup(argv
[share__optind
++]);
196 if(options
->args
.checks
.num_major_ops
> 0) {
197 if(options
->args
.checks
.num_major_ops
> 1) {
198 flac_fprintf(stderr
, "ERROR: you may only specify one major operation at a time\n");
201 else if(options
->args
.checks
.num_shorthand_ops
> 0) {
202 flac_fprintf(stderr
, "ERROR: you may not mix shorthand and major operations\n");
207 /* check for only one FLAC file used with certain options */
208 if(options
->num_files
> 1) {
209 if(0 != find_shorthand_operation(options
, OP__IMPORT_CUESHEET_FROM
)) {
210 flac_fprintf(stderr
, "ERROR: you may only specify one FLAC file when using '--import-cuesheet-from'\n");
213 if(0 != find_shorthand_operation(options
, OP__EXPORT_CUESHEET_TO
)) {
214 flac_fprintf(stderr
, "ERROR: you may only specify one FLAC file when using '--export-cuesheet-to'\n");
217 if(0 != find_shorthand_operation(options
, OP__EXPORT_PICTURE_TO
)) {
218 flac_fprintf(stderr
, "ERROR: you may only specify one FLAC file when using '--export-picture-to'\n");
222 0 != find_shorthand_operation(options
, OP__IMPORT_VC_FROM
) &&
223 0 == strcmp(find_shorthand_operation(options
, OP__IMPORT_VC_FROM
)->argument
.filename
.value
, "-")
225 flac_fprintf(stderr
, "ERROR: you may only specify one FLAC file when using '--import-tags-from=-'\n");
230 if(options
->args
.checks
.has_block_type
&& options
->args
.checks
.has_except_block_type
) {
231 flac_fprintf(stderr
, "ERROR: you may not specify both '--block-type' and '--except-block-type'\n");
239 * We need to create an OP__ADD_SEEKPOINT operation if there is
240 * not one already, and --import-cuesheet-from was specified but
241 * --no-cued-seekpoints was not:
243 if(options
->cued_seekpoints
) {
244 Operation
*op
= find_shorthand_operation(options
, OP__IMPORT_CUESHEET_FROM
);
246 Operation
*op2
= find_shorthand_operation(options
, OP__ADD_SEEKPOINT
);
248 op2
= append_shorthand_operation(options
, OP__ADD_SEEKPOINT
);
249 op
->argument
.import_cuesheet_from
.add_seekpoint_link
= &(op2
->argument
.add_seekpoint
);
256 void free_options(CommandLineOptions
*options
)
262 FLAC__ASSERT(0 == options
->ops
.operations
|| options
->ops
.num_operations
> 0);
263 FLAC__ASSERT(0 == options
->args
.arguments
|| options
->args
.num_arguments
> 0);
265 for(i
= 0, op
= options
->ops
.operations
; i
< options
->ops
.num_operations
; i
++, op
++) {
267 case OP__SHOW_VC_FIELD
:
268 case OP__REMOVE_VC_FIELD
:
269 case OP__REMOVE_VC_FIRSTFIELD
:
270 if(0 != op
->argument
.vc_field_name
.value
)
271 free(op
->argument
.vc_field_name
.value
);
273 case OP__SET_VC_FIELD
:
274 if(0 != op
->argument
.vc_field
.field
)
275 free(op
->argument
.vc_field
.field
);
276 if(0 != op
->argument
.vc_field
.field_name
)
277 free(op
->argument
.vc_field
.field_name
);
278 if(0 != op
->argument
.vc_field
.field_value
)
279 free(op
->argument
.vc_field
.field_value
);
281 case OP__IMPORT_VC_FROM
:
282 case OP__EXPORT_VC_TO
:
283 case OP__EXPORT_CUESHEET_TO
:
284 if(0 != op
->argument
.filename
.value
)
285 free(op
->argument
.filename
.value
);
287 case OP__IMPORT_CUESHEET_FROM
:
288 if(0 != op
->argument
.import_cuesheet_from
.filename
)
289 free(op
->argument
.import_cuesheet_from
.filename
);
291 case OP__IMPORT_PICTURE_FROM
:
292 if(0 != op
->argument
.specification
.value
)
293 free(op
->argument
.specification
.value
);
295 case OP__EXPORT_PICTURE_TO
:
296 if(0 != op
->argument
.export_picture_to
.filename
)
297 free(op
->argument
.export_picture_to
.filename
);
299 case OP__ADD_SEEKPOINT
:
300 if(0 != op
->argument
.add_seekpoint
.specification
)
301 free(op
->argument
.add_seekpoint
.specification
);
308 for(i
= 0, arg
= options
->args
.arguments
; i
< options
->args
.num_arguments
; i
++, arg
++) {
310 case ARG__BLOCK_NUMBER
:
311 if(0 != arg
->value
.block_number
.entries
)
312 free(arg
->value
.block_number
.entries
);
314 case ARG__BLOCK_TYPE
:
315 case ARG__EXCEPT_BLOCK_TYPE
:
316 if(0 != arg
->value
.block_type
.entries
)
317 free(arg
->value
.block_type
.entries
);
320 if(0 != arg
->value
.from_file
.file_name
)
321 free(arg
->value
.from_file
.file_name
);
328 if(0 != options
->ops
.operations
)
329 free(options
->ops
.operations
);
331 if(0 != options
->args
.arguments
)
332 free(options
->args
.arguments
);
334 if(0 != options
->filenames
) {
335 for(i
= 0; i
< options
->num_files
; i
++) {
336 if(0 != options
->filenames
[i
])
337 free(options
->filenames
[i
]);
339 free(options
->filenames
);
347 FLAC__bool
parse_option(int option_index
, const char *option_argument
, CommandLineOptions
*options
)
349 const char *opt
= long_options_
[option_index
].name
;
352 FLAC__bool ok
= true;
354 if(0 == strcmp(opt
, "preserve-modtime")) {
355 options
->preserve_modtime
= true;
357 else if(0 == strcmp(opt
, "with-filename")) {
358 options
->prefix_with_filename
= true;
360 else if(0 == strcmp(opt
, "no-filename")) {
361 options
->prefix_with_filename
= false;
363 else if(0 == strcmp(opt
, "no-utf8-convert")) {
364 options
->utf8_convert
= false;
366 else if(0 == strcmp(opt
, "dont-use-padding")) {
367 options
->use_padding
= false;
369 else if(0 == strcmp(opt
, "no-cued-seekpoints")) {
370 options
->cued_seekpoints
= false;
372 else if(0 == strcmp(opt
, "show-md5sum")) {
373 (void) append_shorthand_operation(options
, OP__SHOW_MD5SUM
);
375 else if(0 == strcmp(opt
, "show-min-blocksize")) {
376 (void) append_shorthand_operation(options
, OP__SHOW_MIN_BLOCKSIZE
);
378 else if(0 == strcmp(opt
, "show-max-blocksize")) {
379 (void) append_shorthand_operation(options
, OP__SHOW_MAX_BLOCKSIZE
);
381 else if(0 == strcmp(opt
, "show-min-framesize")) {
382 (void) append_shorthand_operation(options
, OP__SHOW_MIN_FRAMESIZE
);
384 else if(0 == strcmp(opt
, "show-max-framesize")) {
385 (void) append_shorthand_operation(options
, OP__SHOW_MAX_FRAMESIZE
);
387 else if(0 == strcmp(opt
, "show-sample-rate")) {
388 (void) append_shorthand_operation(options
, OP__SHOW_SAMPLE_RATE
);
390 else if(0 == strcmp(opt
, "show-channels")) {
391 (void) append_shorthand_operation(options
, OP__SHOW_CHANNELS
);
393 else if(0 == strcmp(opt
, "show-bps")) {
394 (void) append_shorthand_operation(options
, OP__SHOW_BPS
);
396 else if(0 == strcmp(opt
, "show-total-samples")) {
397 (void) append_shorthand_operation(options
, OP__SHOW_TOTAL_SAMPLES
);
399 else if(0 == strcmp(opt
, "set-md5sum")) {
400 op
= append_shorthand_operation(options
, OP__SET_MD5SUM
);
401 FLAC__ASSERT(0 != option_argument
);
402 if(!parse_md5(option_argument
, op
->argument
.streaminfo_md5
.value
)) {
403 flac_fprintf(stderr
, "ERROR (--%s): bad MD5 sum\n", opt
);
407 undocumented_warning(opt
);
409 else if(0 == strcmp(opt
, "set-min-blocksize")) {
410 op
= append_shorthand_operation(options
, OP__SET_MIN_BLOCKSIZE
);
411 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || op
->argument
.streaminfo_uint32
.value
< FLAC__MIN_BLOCK_SIZE
|| op
->argument
.streaminfo_uint32
.value
> FLAC__MAX_BLOCK_SIZE
) {
412 flac_fprintf(stderr
, "ERROR (--%s): value must be >= %u and <= %u\n", opt
, FLAC__MIN_BLOCK_SIZE
, FLAC__MAX_BLOCK_SIZE
);
416 undocumented_warning(opt
);
418 else if(0 == strcmp(opt
, "set-max-blocksize")) {
419 op
= append_shorthand_operation(options
, OP__SET_MAX_BLOCKSIZE
);
420 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || op
->argument
.streaminfo_uint32
.value
< FLAC__MIN_BLOCK_SIZE
|| op
->argument
.streaminfo_uint32
.value
> FLAC__MAX_BLOCK_SIZE
) {
421 flac_fprintf(stderr
, "ERROR (--%s): value must be >= %u and <= %u\n", opt
, FLAC__MIN_BLOCK_SIZE
, FLAC__MAX_BLOCK_SIZE
);
425 undocumented_warning(opt
);
427 else if(0 == strcmp(opt
, "set-min-framesize")) {
428 op
= append_shorthand_operation(options
, OP__SET_MIN_FRAMESIZE
);
429 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || op
->argument
.streaminfo_uint32
.value
>= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN
)) {
430 flac_fprintf(stderr
, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt
, FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN
);
434 undocumented_warning(opt
);
436 else if(0 == strcmp(opt
, "set-max-framesize")) {
437 op
= append_shorthand_operation(options
, OP__SET_MAX_FRAMESIZE
);
438 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || op
->argument
.streaminfo_uint32
.value
>= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN
)) {
439 flac_fprintf(stderr
, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt
, FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN
);
443 undocumented_warning(opt
);
445 else if(0 == strcmp(opt
, "set-sample-rate")) {
446 op
= append_shorthand_operation(options
, OP__SET_SAMPLE_RATE
);
447 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || !FLAC__format_sample_rate_is_valid(op
->argument
.streaminfo_uint32
.value
)) {
448 flac_fprintf(stderr
, "ERROR (--%s): invalid sample rate\n", opt
);
452 undocumented_warning(opt
);
454 else if(0 == strcmp(opt
, "set-channels")) {
455 op
= append_shorthand_operation(options
, OP__SET_CHANNELS
);
456 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || op
->argument
.streaminfo_uint32
.value
> FLAC__MAX_CHANNELS
) {
457 flac_fprintf(stderr
, "ERROR (--%s): value must be > 0 and <= %u\n", opt
, FLAC__MAX_CHANNELS
);
461 undocumented_warning(opt
);
463 else if(0 == strcmp(opt
, "set-bps")) {
464 op
= append_shorthand_operation(options
, OP__SET_BPS
);
465 if(!parse_uint32(option_argument
, &(op
->argument
.streaminfo_uint32
.value
)) || op
->argument
.streaminfo_uint32
.value
< FLAC__MIN_BITS_PER_SAMPLE
|| op
->argument
.streaminfo_uint32
.value
> FLAC__MAX_BITS_PER_SAMPLE
) {
466 flac_fprintf(stderr
, "ERROR (--%s): value must be >= %u and <= %u\n", opt
, FLAC__MIN_BITS_PER_SAMPLE
, FLAC__MAX_BITS_PER_SAMPLE
);
470 undocumented_warning(opt
);
472 else if(0 == strcmp(opt
, "set-total-samples")) {
473 op
= append_shorthand_operation(options
, OP__SET_TOTAL_SAMPLES
);
474 if(!parse_uint64(option_argument
, &(op
->argument
.streaminfo_uint64
.value
)) || op
->argument
.streaminfo_uint64
.value
>= (((FLAC__uint64
)1)<<FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN
)) {
475 flac_fprintf(stderr
, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt
, FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN
);
479 undocumented_warning(opt
);
481 else if(0 == strcmp(opt
, "show-vendor-tag")) {
482 (void) append_shorthand_operation(options
, OP__SHOW_VC_VENDOR
);
484 else if(0 == strcmp(opt
, "show-tag")) {
485 const char *violation
;
486 op
= append_shorthand_operation(options
, OP__SHOW_VC_FIELD
);
487 FLAC__ASSERT(0 != option_argument
);
488 if(!parse_vorbis_comment_field_name(option_argument
, &(op
->argument
.vc_field_name
.value
), &violation
)) {
489 FLAC__ASSERT(0 != violation
);
490 flac_fprintf(stderr
, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n %s\n", opt
, option_argument
, violation
);
494 else if(0 == strcmp(opt
, "remove-all-tags")) {
495 (void) append_shorthand_operation(options
, OP__REMOVE_VC_ALL
);
497 else if(0 == strcmp(opt
, "remove-tag")) {
498 const char *violation
;
499 op
= append_shorthand_operation(options
, OP__REMOVE_VC_FIELD
);
500 FLAC__ASSERT(0 != option_argument
);
501 if(!parse_vorbis_comment_field_name(option_argument
, &(op
->argument
.vc_field_name
.value
), &violation
)) {
502 FLAC__ASSERT(0 != violation
);
503 flac_fprintf(stderr
, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n %s\n", opt
, option_argument
, violation
);
507 else if(0 == strcmp(opt
, "remove-first-tag")) {
508 const char *violation
;
509 op
= append_shorthand_operation(options
, OP__REMOVE_VC_FIRSTFIELD
);
510 FLAC__ASSERT(0 != option_argument
);
511 if(!parse_vorbis_comment_field_name(option_argument
, &(op
->argument
.vc_field_name
.value
), &violation
)) {
512 FLAC__ASSERT(0 != violation
);
513 flac_fprintf(stderr
, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n %s\n", opt
, option_argument
, violation
);
517 else if(0 == strcmp(opt
, "set-tag")) {
518 const char *violation
;
519 op
= append_shorthand_operation(options
, OP__SET_VC_FIELD
);
520 FLAC__ASSERT(0 != option_argument
);
521 op
->argument
.vc_field
.field_value_from_file
= false;
522 if(!parse_vorbis_comment_field(option_argument
, &(op
->argument
.vc_field
.field
), &(op
->argument
.vc_field
.field_name
), &(op
->argument
.vc_field
.field_value
), &(op
->argument
.vc_field
.field_value_length
), &violation
)) {
523 FLAC__ASSERT(0 != violation
);
524 flac_fprintf(stderr
, "ERROR (--%s): malformed vorbis comment field \"%s\",\n %s\n", opt
, option_argument
, violation
);
528 else if(0 == strcmp(opt
, "set-tag-from-file")) {
529 const char *violation
;
530 op
= append_shorthand_operation(options
, OP__SET_VC_FIELD
);
531 FLAC__ASSERT(0 != option_argument
);
532 op
->argument
.vc_field
.field_value_from_file
= true;
533 if(!parse_vorbis_comment_field(option_argument
, &(op
->argument
.vc_field
.field
), &(op
->argument
.vc_field
.field_name
), &(op
->argument
.vc_field
.field_value
), &(op
->argument
.vc_field
.field_value_length
), &violation
)) {
534 FLAC__ASSERT(0 != violation
);
535 flac_fprintf(stderr
, "ERROR (--%s): malformed vorbis comment field \"%s\",\n %s\n", opt
, option_argument
, violation
);
539 else if(0 == strcmp(opt
, "import-tags-from")) {
540 op
= append_shorthand_operation(options
, OP__IMPORT_VC_FROM
);
541 FLAC__ASSERT(0 != option_argument
);
542 if(!parse_string(option_argument
, &(op
->argument
.filename
.value
))) {
543 flac_fprintf(stderr
, "ERROR (--%s): missing filename\n", opt
);
547 else if(0 == strcmp(opt
, "export-tags-to")) {
548 op
= append_shorthand_operation(options
, OP__EXPORT_VC_TO
);
549 FLAC__ASSERT(0 != option_argument
);
550 if(!parse_string(option_argument
, &(op
->argument
.filename
.value
))) {
551 flac_fprintf(stderr
, "ERROR (--%s): missing filename\n", opt
);
555 else if(0 == strcmp(opt
, "import-cuesheet-from")) {
556 if(0 != find_shorthand_operation(options
, OP__IMPORT_CUESHEET_FROM
)) {
557 flac_fprintf(stderr
, "ERROR (--%s): may be specified only once\n", opt
);
560 op
= append_shorthand_operation(options
, OP__IMPORT_CUESHEET_FROM
);
561 FLAC__ASSERT(0 != option_argument
);
562 if(!parse_string(option_argument
, &(op
->argument
.import_cuesheet_from
.filename
))) {
563 flac_fprintf(stderr
, "ERROR (--%s): missing filename\n", opt
);
567 else if(0 == strcmp(opt
, "export-cuesheet-to")) {
568 op
= append_shorthand_operation(options
, OP__EXPORT_CUESHEET_TO
);
569 FLAC__ASSERT(0 != option_argument
);
570 if(!parse_string(option_argument
, &(op
->argument
.filename
.value
))) {
571 flac_fprintf(stderr
, "ERROR (--%s): missing filename\n", opt
);
575 else if(0 == strcmp(opt
, "import-picture-from")) {
576 op
= append_shorthand_operation(options
, OP__IMPORT_PICTURE_FROM
);
577 FLAC__ASSERT(0 != option_argument
);
578 if(!parse_string(option_argument
, &(op
->argument
.specification
.value
))) {
579 flac_fprintf(stderr
, "ERROR (--%s): missing specification\n", opt
);
583 else if(0 == strcmp(opt
, "export-picture-to")) {
584 arg
= find_argument(options
, ARG__BLOCK_NUMBER
);
585 op
= append_shorthand_operation(options
, OP__EXPORT_PICTURE_TO
);
586 FLAC__ASSERT(0 != option_argument
);
587 if(!parse_string(option_argument
, &(op
->argument
.export_picture_to
.filename
))) {
588 flac_fprintf(stderr
, "ERROR (--%s): missing filename\n", opt
);
591 op
->argument
.export_picture_to
.block_number_link
= arg
? &(arg
->value
.block_number
) : 0;
593 else if(0 == strcmp(opt
, "add-seekpoint")) {
594 const char *violation
;
596 FLAC__ASSERT(0 != option_argument
);
597 if(!parse_add_seekpoint(option_argument
, &spec
, &violation
)) {
598 FLAC__ASSERT(0 != violation
);
599 flac_fprintf(stderr
, "ERROR (--%s): malformed seekpoint specification \"%s\",\n %s\n", opt
, option_argument
, violation
);
603 op
= find_shorthand_operation(options
, OP__ADD_SEEKPOINT
);
605 op
= append_shorthand_operation(options
, OP__ADD_SEEKPOINT
);
606 local_strcat(&(op
->argument
.add_seekpoint
.specification
), spec
);
607 local_strcat(&(op
->argument
.add_seekpoint
.specification
), ";");
611 else if(0 == strcmp(opt
, "add-replay-gain")) {
612 (void) append_shorthand_operation(options
, OP__ADD_REPLAY_GAIN
);
614 else if(0 == strcmp(opt
, "remove-replay-gain")) {
615 const FLAC__byte
* const tags
[5] = {
616 GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS
,
617 GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN
,
618 GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK
,
619 GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN
,
620 GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK
623 for(i
= 0; i
< sizeof(tags
)/sizeof(tags
[0]); i
++) {
624 op
= append_shorthand_operation(options
, OP__REMOVE_VC_FIELD
);
625 op
->argument
.vc_field_name
.value
= local_strdup((const char *)tags
[i
]);
628 else if(0 == strcmp(opt
, "add-padding")) {
629 op
= append_shorthand_operation(options
, OP__ADD_PADDING
);
630 FLAC__ASSERT(0 != option_argument
);
631 if(!parse_add_padding(option_argument
, &(op
->argument
.add_padding
.length
))) {
632 flac_fprintf(stderr
, "ERROR (--%s): illegal length \"%s\", length must be >= 0 and < 2^%u\n", opt
, option_argument
, FLAC__STREAM_METADATA_LENGTH_LEN
);
636 else if(0 == strcmp(opt
, "help")) {
637 options
->show_long_help
= true;
639 else if(0 == strcmp(opt
, "version")) {
640 options
->show_version
= true;
642 else if(0 == strcmp(opt
, "list")) {
643 (void) append_major_operation(options
, OP__LIST
);
645 else if(0 == strcmp(opt
, "append")) {
646 (void) append_major_operation(options
, OP__APPEND
);
648 else if(0 == strcmp(opt
, "remove")) {
649 (void) append_major_operation(options
, OP__REMOVE
);
651 else if(0 == strcmp(opt
, "remove-all")) {
652 (void) append_major_operation(options
, OP__REMOVE_ALL
);
654 else if(0 == strcmp(opt
, "merge-padding")) {
655 (void) append_major_operation(options
, OP__MERGE_PADDING
);
657 else if(0 == strcmp(opt
, "sort-padding")) {
658 (void) append_major_operation(options
, OP__SORT_PADDING
);
660 else if(0 == strcmp(opt
, "block-number")) {
661 arg
= append_argument(options
, ARG__BLOCK_NUMBER
);
662 FLAC__ASSERT(0 != option_argument
);
663 if(!parse_block_number(option_argument
, &(arg
->value
.block_number
))) {
664 flac_fprintf(stderr
, "ERROR: malformed block number specification \"%s\"\n", option_argument
);
668 else if(0 == strcmp(opt
, "block-type")) {
669 arg
= append_argument(options
, ARG__BLOCK_TYPE
);
670 FLAC__ASSERT(0 != option_argument
);
671 if(!parse_block_type(option_argument
, &(arg
->value
.block_type
))) {
672 flac_fprintf(stderr
, "ERROR (--%s): malformed block type specification \"%s\"\n", opt
, option_argument
);
675 options
->args
.checks
.has_block_type
= true;
677 else if(0 == strcmp(opt
, "except-block-type")) {
678 arg
= append_argument(options
, ARG__EXCEPT_BLOCK_TYPE
);
679 FLAC__ASSERT(0 != option_argument
);
680 if(!parse_block_type(option_argument
, &(arg
->value
.block_type
))) {
681 flac_fprintf(stderr
, "ERROR (--%s): malformed block type specification \"%s\"\n", opt
, option_argument
);
684 options
->args
.checks
.has_except_block_type
= true;
686 else if(0 == strcmp(opt
, "data-format")) {
687 arg
= append_argument(options
, ARG__DATA_FORMAT
);
688 FLAC__ASSERT(0 != option_argument
);
689 if(!parse_data_format(option_argument
, &(arg
->value
.data_format
))) {
690 flac_fprintf(stderr
, "ERROR (--%s): illegal data format \"%s\"\n", opt
, option_argument
);
694 else if(0 == strcmp(opt
, "application-data-format")) {
695 FLAC__ASSERT(0 != option_argument
);
696 if(!parse_application_data_format(option_argument
, &(options
->application_data_format_is_hexdump
))) {
697 flac_fprintf(stderr
, "ERROR (--%s): illegal application data format \"%s\"\n", opt
, option_argument
);
701 else if(0 == strcmp(opt
, "from-file")) {
702 arg
= append_argument(options
, ARG__FROM_FILE
);
703 FLAC__ASSERT(0 != option_argument
);
704 arg
->value
.from_file
.file_name
= local_strdup(option_argument
);
713 void append_new_operation(CommandLineOptions
*options
, Operation operation
)
715 if(options
->ops
.capacity
== 0) {
716 options
->ops
.capacity
= 50;
717 if(0 == (options
->ops
.operations
= malloc(sizeof(Operation
) * options
->ops
.capacity
)))
718 die("out of memory allocating space for option list");
719 memset(options
->ops
.operations
, 0, sizeof(Operation
) * options
->ops
.capacity
);
721 if(options
->ops
.capacity
<= options
->ops
.num_operations
) {
722 unsigned original_capacity
= options
->ops
.capacity
;
723 if(options
->ops
.capacity
> UINT32_MAX
/ 2) /* overflow check */
724 die("out of memory allocating space for option list");
725 options
->ops
.capacity
*= 2;
726 if(0 == (options
->ops
.operations
= safe_realloc_mul_2op_(options
->ops
.operations
, sizeof(Operation
), /*times*/options
->ops
.capacity
)))
727 die("out of memory allocating space for option list");
728 memset(options
->ops
.operations
+ original_capacity
, 0, sizeof(Operation
) * (options
->ops
.capacity
- original_capacity
));
731 options
->ops
.operations
[options
->ops
.num_operations
++] = operation
;
734 void append_new_argument(CommandLineOptions
*options
, Argument argument
)
736 if(options
->args
.capacity
== 0) {
737 options
->args
.capacity
= 50;
738 if(0 == (options
->args
.arguments
= malloc(sizeof(Argument
) * options
->args
.capacity
)))
739 die("out of memory allocating space for option list");
740 memset(options
->args
.arguments
, 0, sizeof(Argument
) * options
->args
.capacity
);
742 if(options
->args
.capacity
<= options
->args
.num_arguments
) {
743 unsigned original_capacity
= options
->args
.capacity
;
744 if(options
->args
.capacity
> UINT32_MAX
/ 2) /* overflow check */
745 die("out of memory allocating space for option list");
746 options
->args
.capacity
*= 2;
747 if(0 == (options
->args
.arguments
= safe_realloc_mul_2op_(options
->args
.arguments
, sizeof(Argument
), /*times*/options
->args
.capacity
)))
748 die("out of memory allocating space for option list");
749 memset(options
->args
.arguments
+ original_capacity
, 0, sizeof(Argument
) * (options
->args
.capacity
- original_capacity
));
752 options
->args
.arguments
[options
->args
.num_arguments
++] = argument
;
755 Operation
*append_major_operation(CommandLineOptions
*options
, OperationType type
)
758 memset(&op
, 0, sizeof(op
));
760 append_new_operation(options
, op
);
761 options
->args
.checks
.num_major_ops
++;
762 return options
->ops
.operations
+ (options
->ops
.num_operations
- 1);
765 Operation
*append_shorthand_operation(CommandLineOptions
*options
, OperationType type
)
768 memset(&op
, 0, sizeof(op
));
770 append_new_operation(options
, op
);
771 options
->args
.checks
.num_shorthand_ops
++;
772 return options
->ops
.operations
+ (options
->ops
.num_operations
- 1);
775 Argument
*find_argument(CommandLineOptions
*options
, ArgumentType type
)
778 for(i
= 0; i
< options
->args
.num_arguments
; i
++)
779 if(options
->args
.arguments
[i
].type
== type
)
780 return &options
->args
.arguments
[i
];
784 Operation
*find_shorthand_operation(CommandLineOptions
*options
, OperationType type
)
787 for(i
= 0; i
< options
->ops
.num_operations
; i
++)
788 if(options
->ops
.operations
[i
].type
== type
)
789 return &options
->ops
.operations
[i
];
793 Argument
*append_argument(CommandLineOptions
*options
, ArgumentType type
)
796 memset(&arg
, 0, sizeof(arg
));
798 append_new_argument(options
, arg
);
799 return options
->args
.arguments
+ (options
->args
.num_arguments
- 1);
802 FLAC__bool
parse_md5(const char *src
, FLAC__byte dest
[16])
806 FLAC__ASSERT(0 != src
);
807 if(strlen(src
) != 32)
809 /* strtoul() accepts negative numbers which we do not want, so we do it the hard way */
810 for(i
= 0; i
< 16; i
++) {
813 d
= (unsigned)(c
- '0');
814 else if(c
>= 'a' && c
<= 'f')
815 d
= (unsigned)(c
- 'a') + 10u;
816 else if(c
>= 'A' && c
<= 'F')
817 d
= (unsigned)(c
- 'A') + 10u;
823 d
|= (unsigned)(c
- '0');
824 else if(c
>= 'a' && c
<= 'f')
825 d
|= (unsigned)(c
- 'a') + 10u;
826 else if(c
>= 'A' && c
<= 'F')
827 d
|= (unsigned)(c
- 'A') + 10u;
830 dest
[i
] = (FLAC__byte
)d
;
835 FLAC__bool
parse_uint32(const char *src
, FLAC__uint32
*dest
)
837 FLAC__ASSERT(0 != src
);
838 if(strlen(src
) == 0 || strspn(src
, "0123456789") != strlen(src
))
840 *dest
= strtoul(src
, 0, 10);
844 FLAC__bool
parse_uint64(const char *src
, FLAC__uint64
*dest
)
846 FLAC__ASSERT(0 != src
);
847 if(strlen(src
) == 0 || strspn(src
, "0123456789") != strlen(src
))
849 *dest
= strtoull(src
, 0, 10);
853 FLAC__bool
parse_string(const char *src
, char **dest
)
855 if(0 == src
|| strlen(src
) == 0)
861 FLAC__bool
parse_vorbis_comment_field_name(const char *field_ref
, char **name
, const char **violation
)
863 static const char * const violations
[] = {
864 "field name contains invalid character"
869 s
= local_strdup(field_ref
);
871 for(q
= s
; *q
; q
++) {
872 if(*q
< 0x20 || *q
> 0x7d || *q
== 0x3d) {
874 *violation
= violations
[0];
884 FLAC__bool
parse_add_seekpoint(const char *in
, char **out
, const char **violation
)
886 static const char *garbled_
= "garbled specification";
887 const unsigned n
= strlen(in
);
889 FLAC__ASSERT(0 != in
);
890 FLAC__ASSERT(0 != out
);
893 *violation
= "specification is empty";
897 if(n
> strspn(in
, "0123456789.Xsx")) {
898 *violation
= "specification contains invalid character";
904 *violation
= garbled_
;
908 else if(in
[n
-1] == 's') {
909 if(n
-1 > strspn(in
, "0123456789.")) {
910 *violation
= garbled_
;
914 else if(in
[n
-1] == 'x') {
915 if(n
-1 > strspn(in
, "0123456789")) {
916 *violation
= garbled_
;
921 if(n
> strspn(in
, "0123456789")) {
922 *violation
= garbled_
;
927 *out
= local_strdup(in
);
931 FLAC__bool
parse_add_padding(const char *in
, unsigned *out
)
933 FLAC__ASSERT(0 != in
);
934 FLAC__ASSERT(0 != out
);
935 *out
= (unsigned)strtoul(in
, 0, 10);
936 return *out
< (1u << FLAC__STREAM_METADATA_LENGTH_LEN
);
939 FLAC__bool
parse_block_number(const char *in
, Argument_BlockNumber
*out
)
941 char *p
, *q
, *s
, *end
;
948 s
= local_strdup(in
);
950 /* first count the entries */
951 for(out
->num_entries
= 1, p
= strchr(s
, ','); p
; out
->num_entries
++, p
= strchr(++p
, ','))
955 FLAC__ASSERT(out
->num_entries
> 0);
956 if(0 == (out
->entries
= safe_malloc_mul_2op_(sizeof(unsigned), /*times*/out
->num_entries
)))
957 die("out of memory allocating space for option list");
963 FLAC__ASSERT(entry
< out
->num_entries
);
964 if(0 != (p
= strchr(q
, ',')))
966 if(!isdigit((int)(*q
)) || (i
= strtol(q
, &end
, 10)) < 0 || *end
) {
970 out
->entries
[entry
++] = (unsigned)i
;
973 FLAC__ASSERT(entry
== out
->num_entries
);
979 FLAC__bool
parse_block_type(const char *in
, Argument_BlockType
*out
)
987 s
= local_strdup(in
);
989 /* first count the entries */
990 for(out
->num_entries
= 1, p
= strchr(s
, ','); p
; out
->num_entries
++, p
= strchr(++p
, ','))
994 FLAC__ASSERT(out
->num_entries
> 0);
995 if(0 == (out
->entries
= safe_malloc_mul_2op_(sizeof(Argument_BlockTypeEntry
), /*times*/out
->num_entries
)))
996 die("out of memory allocating space for option list");
1002 FLAC__ASSERT(entry
< out
->num_entries
);
1003 if(0 != (p
= strchr(q
, ',')))
1008 if(0 != r
&& 0 != strcmp(q
, "APPLICATION")) {
1012 if(0 == strcmp(q
, "STREAMINFO")) {
1013 out
->entries
[entry
++].type
= FLAC__METADATA_TYPE_STREAMINFO
;
1015 else if(0 == strcmp(q
, "PADDING")) {
1016 out
->entries
[entry
++].type
= FLAC__METADATA_TYPE_PADDING
;
1018 else if(0 == strcmp(q
, "APPLICATION")) {
1019 out
->entries
[entry
].type
= FLAC__METADATA_TYPE_APPLICATION
;
1020 out
->entries
[entry
].filter_application_by_id
= (0 != r
);
1022 if(strlen(r
) == sizeof (out
->entries
[entry
].application_id
)) {
1023 memcpy(out
->entries
[entry
].application_id
, r
, sizeof (out
->entries
[entry
].application_id
));
1025 else if(strlen(r
) == 10 && strncmp(r
, "0x", 2) == 0 && strspn(r
+2, "0123456789ABCDEFabcdef") == 8) {
1026 FLAC__uint32 x
= strtoul(r
+2, 0, 16);
1027 out
->entries
[entry
].application_id
[3] = (FLAC__byte
)(x
& 0xff);
1028 out
->entries
[entry
].application_id
[2] = (FLAC__byte
)((x
>>=8) & 0xff);
1029 out
->entries
[entry
].application_id
[1] = (FLAC__byte
)((x
>>=8) & 0xff);
1030 out
->entries
[entry
].application_id
[0] = (FLAC__byte
)((x
>>=8) & 0xff);
1039 else if(0 == strcmp(q
, "SEEKTABLE")) {
1040 out
->entries
[entry
++].type
= FLAC__METADATA_TYPE_SEEKTABLE
;
1042 else if(0 == strcmp(q
, "VORBIS_COMMENT")) {
1043 out
->entries
[entry
++].type
= FLAC__METADATA_TYPE_VORBIS_COMMENT
;
1045 else if(0 == strcmp(q
, "CUESHEET")) {
1046 out
->entries
[entry
++].type
= FLAC__METADATA_TYPE_CUESHEET
;
1048 else if(0 == strcmp(q
, "PICTURE")) {
1049 out
->entries
[entry
++].type
= FLAC__METADATA_TYPE_PICTURE
;
1057 FLAC__ASSERT(entry
== out
->num_entries
);
1063 FLAC__bool
parse_data_format(const char *in
, Argument_DataFormat
*out
)
1065 if(0 == strcmp(in
, "binary"))
1066 out
->is_binary
= true;
1067 else if(0 == strcmp(in
, "text"))
1068 out
->is_binary
= false;
1074 FLAC__bool
parse_application_data_format(const char *in
, FLAC__bool
*out
)
1076 if(0 == strcmp(in
, "hexdump"))
1078 else if(0 == strcmp(in
, "text"))
1085 void undocumented_warning(const char *opt
)
1087 flac_fprintf(stderr
, "WARNING: undocumented option --%s should be used with caution,\n only for repairing a damaged STREAMINFO block\n", opt
);