main.c 103 KB
Newer Older
Josh Coalson's avatar
Josh Coalson committed
1
/* flac - Command-line FLAC encoder/decoder
2
3
 * Copyright (C) 2000-2009  Josh Coalson
 * Copyright (C) 2011-2013  Xiph.Org Foundation
Josh Coalson's avatar
Josh Coalson committed
4
5
6
7
8
9
10
11
12
13
14
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
Miroslav Lichvar's avatar
Miroslav Lichvar committed
15
16
17
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Josh Coalson's avatar
Josh Coalson committed
18
19
 */

Josh Coalson's avatar
Josh Coalson committed
20
21
22
23
#if HAVE_CONFIG_H
#  include <config.h>
#endif

Josh Coalson's avatar
Josh Coalson committed
24
#include <ctype.h>
Josh Coalson's avatar
Josh Coalson committed
25
#include <errno.h>
Josh Coalson's avatar
Josh Coalson committed
26
#include <locale.h>
Josh Coalson's avatar
Josh Coalson committed
27
28
29
30
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
31
#include <time.h>
32

33
#if !defined _MSC_VER && !defined __MINGW32__
Josh Coalson's avatar
Josh Coalson committed
34
/* unlink is in stdio.h in VC++ */
Josh Coalson's avatar
Josh Coalson committed
35
#include <unistd.h> /* for unlink() */
Josh Coalson's avatar
Josh Coalson committed
36
#endif
Josh Coalson's avatar
Josh Coalson committed
37
#include "FLAC/all.h"
38
#include "share/alloc.h"
Josh Coalson's avatar
Josh Coalson committed
39
#include "share/grabbag.h"
40
#include "share/compat.h"
41
#include "share/safe_str.h"
Josh Coalson's avatar
Josh Coalson committed
42
#include "analyze.h"
Josh Coalson's avatar
Josh Coalson committed
43
44
#include "decode.h"
#include "encode.h"
45
#include "local_string_utils.h" /* for flac__strlcat() and flac__strlcpy() */
46
#include "utils.h"
Josh Coalson's avatar
Josh Coalson committed
47
#include "vorbiscomment.h"
Josh Coalson's avatar
Josh Coalson committed
48

49
#if defined _MSC_VER || defined __MINGW32__
50
51
52
53
54
#define FLAC__STRCASECMP stricmp
#else
#define FLAC__STRCASECMP strcasecmp
#endif

55
56
57
58
59
60
61
62
#if 0
/*[JEC] was:#if HAVE_GETOPT_LONG*/
/*[JEC] see flac/include/share/getopt.h as to why the change */
#  include <getopt.h>
#else
#  include "share/getopt.h"
#endif

63
static int do_it(void);
64

65
static FLAC__bool init_options(void);
66
67
static int parse_options(int argc, char *argv[]);
static int parse_option(int short_option, const char *long_option, const char *option_argument);
68
static void free_options(void);
69
70
71
static void add_compression_setting_bool(compression_setting_type_t type, FLAC__bool value);
static void add_compression_setting_string(compression_setting_type_t type, const char *value);
static void add_compression_setting_unsigned(compression_setting_type_t type, unsigned value);
72
73

static int usage_error(const char *message, ...);
74
75
76
77
static void short_usage(void);
static void show_version(void);
static void show_help(void);
static void show_explain(void);
78
static void format_mistake(const char *infilename, FileFormat wrong, FileFormat right);
79

80
81
82
83
84
static int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_last_file);
static int decode_file(const char *infilename);

static const char *get_encoded_outfilename(const char *infilename);
static const char *get_decoded_outfilename(const char *infilename);
85
static const char *get_outfilename(const char *infilename, const char *suffix);
86

87
static void die(const char *message);
Josh Coalson's avatar
Josh Coalson committed
88
static int conditional_fclose(FILE *f);
89
90
91
static char *local_strdup(const char *source);

/*
Josh Coalson's avatar
Josh Coalson committed
92
 * share__getopt format struct; note that for long options with no
93
94
 * short option equivalent we just set the 'val' field to 0.
 */
Josh Coalson's avatar
Josh Coalson committed
95
static struct share__option long_options_[] = {
96
97
98
	/*
	 * general options
	 */
99
100
101
102
103
104
105
106
107
108
109
110
	{ "help"                  , share__no_argument, 0, 'h' },
	{ "explain"               , share__no_argument, 0, 'H' },
	{ "version"               , share__no_argument, 0, 'v' },
	{ "decode"                , share__no_argument, 0, 'd' },
	{ "analyze"               , share__no_argument, 0, 'a' },
	{ "test"                  , share__no_argument, 0, 't' },
	{ "stdout"                , share__no_argument, 0, 'c' },
	{ "silent"                , share__no_argument, 0, 's' },
	{ "totally-silent"        , share__no_argument, 0, 0 },
	{ "warnings-as-errors"    , share__no_argument, 0, 'w' },
	{ "force"                 , share__no_argument, 0, 'f' },
	{ "delete-input-file"     , share__no_argument, 0, 0 },
111
	{ "preserve-modtime"      , share__no_argument, 0, 0 },
112
113
114
115
116
117
	{ "keep-foreign-metadata" , share__no_argument, 0, 0 },
	{ "output-prefix"         , share__required_argument, 0, 0 },
	{ "output-name"           , share__required_argument, 0, 'o' },
	{ "skip"                  , share__required_argument, 0, 0 },
	{ "until"                 , share__required_argument, 0, 0 },
	{ "channel-map"           , share__required_argument, 0, 0 }, /* undocumented */
118
119
120
121

	/*
	 * decoding options
	 */
122
	{ "decode-through-errors", share__no_argument, 0, 'F' },
123
	{ "cue"                  , share__required_argument, 0, 0 },
124
	{ "apply-replaygain-which-is-not-lossless", share__optional_argument, 0, 0 }, /* undocumented */
125
126
127
128

	/*
	 * encoding options
	 */
129
130
	{ "cuesheet"                  , share__required_argument, 0, 0 },
	{ "no-cued-seekpoints"        , share__no_argument, 0, 0 },
131
	{ "picture"                   , share__required_argument, 0, 0 },
132
	{ "tag"                       , share__required_argument, 0, 'T' },
133
	{ "tag-from-file"             , share__required_argument, 0, 0 },
134
135
136
137
138
139
140
141
142
143
144
145
146
	{ "compression-level-0"       , share__no_argument, 0, '0' },
	{ "compression-level-1"       , share__no_argument, 0, '1' },
	{ "compression-level-2"       , share__no_argument, 0, '2' },
	{ "compression-level-3"       , share__no_argument, 0, '3' },
	{ "compression-level-4"       , share__no_argument, 0, '4' },
	{ "compression-level-5"       , share__no_argument, 0, '5' },
	{ "compression-level-6"       , share__no_argument, 0, '6' },
	{ "compression-level-7"       , share__no_argument, 0, '7' },
	{ "compression-level-8"       , share__no_argument, 0, '8' },
	{ "compression-level-9"       , share__no_argument, 0, '9' },
	{ "best"                      , share__no_argument, 0, '8' },
	{ "fast"                      , share__no_argument, 0, '0' },
	{ "verify"                    , share__no_argument, 0, 'V' },
147
	{ "force-raw-format"          , share__no_argument, 0, 0 },
148
	{ "force-aiff-format"         , share__no_argument, 0, 0 },
Josh Coalson's avatar
Josh Coalson committed
149
	{ "force-rf64-format"         , share__no_argument, 0, 0 },
150
	{ "force-wave64-format"       , share__no_argument, 0, 0 },
151
152
	{ "lax"                       , share__no_argument, 0, 0 },
	{ "replay-gain"               , share__no_argument, 0, 0 },
153
	{ "ignore-chunk-sizes"        , share__no_argument, 0, 0 },
Josh Coalson's avatar
Josh Coalson committed
154
	{ "sector-align"              , share__no_argument, 0, 0 }, /* DEPRECATED */
155
156
	{ "seekpoint"                 , share__required_argument, 0, 'S' },
	{ "padding"                   , share__required_argument, 0, 'P' },
157
#if FLAC__HAS_OGG
158
159
	{ "ogg"                       , share__no_argument, 0, 0 },
	{ "serial-number"             , share__required_argument, 0, 0 },
160
#endif
161
162
163
	{ "blocksize"                 , share__required_argument, 0, 'b' },
	{ "exhaustive-model-search"   , share__no_argument, 0, 'e' },
	{ "max-lpc-order"             , share__required_argument, 0, 'l' },
164
	{ "apodization"               , share__required_argument, 0, 'A' },
165
166
167
168
169
170
171
172
173
174
	{ "mid-side"                  , share__no_argument, 0, 'm' },
	{ "adaptive-mid-side"         , share__no_argument, 0, 'M' },
	{ "qlp-coeff-precision-search", share__no_argument, 0, 'p' },
	{ "qlp-coeff-precision"       , share__required_argument, 0, 'q' },
	{ "rice-partition-order"      , share__required_argument, 0, 'r' },
	{ "endian"                    , share__required_argument, 0, 0 },
	{ "channels"                  , share__required_argument, 0, 0 },
	{ "bps"                       , share__required_argument, 0, 0 },
	{ "sample-rate"               , share__required_argument, 0, 0 },
	{ "sign"                      , share__required_argument, 0, 0 },
Josh Coalson's avatar
Josh Coalson committed
175
	{ "input-size"                , share__required_argument, 0, 0 },
176
	{ "error-on-compression-fail" , share__no_argument, 0, 0 },
177
178
179
180

	/*
	 * analysis options
	 */
181
	{ "residual-gnuplot", share__no_argument, 0, 0 },
182
	{ "residual-text", share__no_argument, 0, 0 },
183
184
185
186

	/*
	 * negatives
	 */
187
	{ "no-preserve-modtime"       , share__no_argument, 0, 0 },
188
189
	{ "no-decode-through-errors"  , share__no_argument, 0, 0 },
	{ "no-silent"                 , share__no_argument, 0, 0 },
190
	{ "no-force"                  , share__no_argument, 0, 0 },
191
192
	{ "no-seektable"              , share__no_argument, 0, 0 },
	{ "no-delete-input-file"      , share__no_argument, 0, 0 },
193
	{ "no-keep-foreign-metadata"  , share__no_argument, 0, 0 },
194
	{ "no-replay-gain"            , share__no_argument, 0, 0 },
195
	{ "no-ignore-chunk-sizes"     , share__no_argument, 0, 0 },
Josh Coalson's avatar
Josh Coalson committed
196
	{ "no-sector-align"           , share__no_argument, 0, 0 }, /* DEPRECATED */
197
	{ "no-utf8-convert"           , share__no_argument, 0, 0 },
198
	{ "no-lax"                    , share__no_argument, 0, 0 },
199
#if FLAC__HAS_OGG
200
	{ "no-ogg"                    , share__no_argument, 0, 0 },
201
#endif
202
203
204
205
206
207
	{ "no-exhaustive-model-search", share__no_argument, 0, 0 },
	{ "no-mid-side"               , share__no_argument, 0, 0 },
	{ "no-adaptive-mid-side"      , share__no_argument, 0, 0 },
	{ "no-qlp-coeff-prec-search"  , share__no_argument, 0, 0 },
	{ "no-padding"                , share__no_argument, 0, 0 },
	{ "no-verify"                 , share__no_argument, 0, 0 },
208
	{ "no-warnings-as-errors"     , share__no_argument, 0, 0 },
209
210
	{ "no-residual-gnuplot"       , share__no_argument, 0, 0 },
	{ "no-residual-text"          , share__no_argument, 0, 0 },
211
	{ "no-error-on-compression-fail", share__no_argument, 0, 0 },
212
213
214
	/*
	 * undocumented debugging options for the test suite
	 */
215
216
217
	{ "disable-constant-subframes", share__no_argument, 0, 0 },
	{ "disable-fixed-subframes"   , share__no_argument, 0, 0 },
	{ "disable-verbatim-subframes", share__no_argument, 0, 0 },
Josh Coalson's avatar
Josh Coalson committed
218
	{ "no-md5-sum"                , share__no_argument, 0, 0 },
219

Josh Coalson's avatar
Josh Coalson committed
220
	{0, 0, 0, 0}
221
222
223
224
225
226
227
228
};


/*
 * global to hold command-line option values
 */

static struct {
229
230
	FLAC__bool show_help;
	FLAC__bool show_explain;
Josh Coalson's avatar
Josh Coalson committed
231
	FLAC__bool show_version;
232
233
	FLAC__bool mode_decode;
	FLAC__bool verify;
234
	FLAC__bool treat_warnings_as_errors;
235
	FLAC__bool force_file_overwrite;
236
	FLAC__bool continue_through_decode_errors;
237
	replaygain_synthesis_spec_t replaygain_synthesis_spec;
238
239
240
241
	FLAC__bool lax;
	FLAC__bool test_only;
	FLAC__bool analyze;
	FLAC__bool use_ogg;
242
243
	FLAC__bool has_serial_number; /* true iff --serial-number was used */
	long serial_number; /* this is the Ogg serial number and is unused for native FLAC */
244
	FLAC__bool force_to_stdout;
245
	FLAC__bool force_raw_format;
246
	FLAC__bool force_aiff_format;
Josh Coalson's avatar
Josh Coalson committed
247
	FLAC__bool force_rf64_format;
248
	FLAC__bool force_wave64_format;
249
	FLAC__bool delete_input;
250
	FLAC__bool preserve_modtime;
251
	FLAC__bool keep_foreign_metadata;
252
	FLAC__bool replay_gain;
253
	FLAC__bool ignore_chunk_sizes;
254
	FLAC__bool sector_align;
255
	FLAC__bool utf8_convert; /* true by default, to convert tag strings from locale to utf-8, false if --no-utf8-convert used */
256
257
258
	const char *cmdline_forced_outfilename;
	const char *output_prefix;
	analysis_options aopts;
259
	int padding; /* -1 => no -P options were given, 0 => -P- was given, else -P value */
260
261
	size_t num_compression_settings;
	compression_setting_t compression_settings[64]; /* bad MAGIC NUMBER but buffer overflow is checked */
262
	const char *skip_specification;
263
	const char *until_specification;
264
	const char *cue_specification;
265
266
267
268
269
	int format_is_big_endian;
	int format_is_unsigned_samples;
	int format_channels;
	int format_bps;
	int format_sample_rate;
270
	FLAC__off_t format_input_size;
271
	char requested_seek_points[5000]; /* bad MAGIC NUMBER but buffer overflow is checked */
272
	int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */
273
274
	const char *cuesheet_filename;
	FLAC__bool cued_seekpoints;
275
	FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */
276
	FLAC__bool error_on_compression_fail;
277
278
279

	unsigned num_files;
	char **filenames;
Josh Coalson's avatar
Josh Coalson committed
280
281

	FLAC__StreamMetadata *vorbis_comment;
282
283
	FLAC__StreamMetadata *pictures[64];
	unsigned num_pictures;
284
285
286
287
288

	struct {
		FLAC__bool disable_constant_subframes;
		FLAC__bool disable_fixed_subframes;
		FLAC__bool disable_verbatim_subframes;
Josh Coalson's avatar
Josh Coalson committed
289
		FLAC__bool do_md5;
290
	} debug;
291
292
293
294
295
296
297
} option_values;


/*
 * miscellaneous globals
 */

Josh Coalson's avatar
Josh Coalson committed
298
static FLAC__int32 align_reservoir_0[588], align_reservoir_1[588]; /* for carrying over samples from --sector-align */ /* DEPRECATED */
299
300
static FLAC__int32 *align_reservoir[2] = { align_reservoir_0, align_reservoir_1 };
static unsigned align_reservoir_samples = 0; /* 0 .. 587 */
Josh Coalson's avatar
Josh Coalson committed
301

302

Josh Coalson's avatar
Josh Coalson committed
303
304
int main(int argc, char *argv[])
{
305
306
	int retval = 0;

307
308
309
310
#ifdef __EMX__
	_response(&argc, &argv);
	_wildcard(&argc, &argv);
#endif
311
#ifdef _WIN32
312
313
314
315
316
	if (get_utf8_argv(&argc, &argv) != 0) {
		fprintf(stderr, "ERROR: failed to convert command line parameters to UTF-8\n");
		return 1;
	}
#endif
317

Josh Coalson's avatar
Josh Coalson committed
318
	srand((unsigned)time(0));
Josh Coalson's avatar
Josh Coalson committed
319
320
	setlocale(LC_ALL, "");
	if(!init_options()) {
321
		flac__utils_printf(stderr, 1, "ERROR: allocating memory\n");
Josh Coalson's avatar
Josh Coalson committed
322
323
324
325
326
327
		retval = 1;
	}
	else {
		if((retval = parse_options(argc, argv)) == 0)
			retval = do_it();
	}
328
329
330
331
332
333

	free_options();

	return retval;
}

334
int do_it(void)
335
336
337
{
	int retval = 0;

Josh Coalson's avatar
Josh Coalson committed
338
339
340
341
342
	if(option_values.show_version) {
		show_version();
		return 0;
	}
	else if(option_values.show_explain) {
343
344
345
346
347
		show_explain();
		return 0;
	}
	else if(option_values.show_help) {
		show_help();
348
349
350
351
		return 0;
	}
	else {
		if(option_values.num_files == 0) {
352
353
			if(flac__utils_verbosity_ >= 1)
				short_usage();
354
355
356
357
358
359
360
			return 0;
		}

		/*
		 * tweak options; validate the values
		 */
		if(!option_values.mode_decode) {
361
362
			if(0 != option_values.cue_specification)
				return usage_error("ERROR: --cue is not allowed in test mode\n");
Josh Coalson's avatar
Josh Coalson committed
363
364
		}
		else {
365
			if(option_values.test_only) {
366
				if(0 != option_values.skip_specification)
367
					return usage_error("ERROR: --skip is not allowed in test mode\n");
368
369
				if(0 != option_values.until_specification)
					return usage_error("ERROR: --until is not allowed in test mode\n");
370
371
				if(0 != option_values.cue_specification)
					return usage_error("ERROR: --cue is not allowed in test mode\n");
372
373
				if(0 != option_values.analyze)
					return usage_error("ERROR: analysis mode (-a/--analyze) and test mode (-t/--test) cannot be used together\n");
374
			}
Josh Coalson's avatar
Josh Coalson committed
375
376
		}

377
378
379
		if(0 != option_values.cue_specification && (0 != option_values.skip_specification || 0 != option_values.until_specification))
			return usage_error("ERROR: --cue may not be combined with --skip or --until\n");

380
381
382
		if(option_values.format_channels >= 0) {
			if(option_values.format_channels == 0 || (unsigned)option_values.format_channels > FLAC__MAX_CHANNELS)
				return usage_error("ERROR: invalid number of channels '%u', must be > 0 and <= %u\n", option_values.format_channels, FLAC__MAX_CHANNELS);
383
		}
384
385
386
		if(option_values.format_bps >= 0) {
			if(option_values.format_bps != 8 && option_values.format_bps != 16 && option_values.format_bps != 24)
				return usage_error("ERROR: invalid bits per sample '%u' (must be 8/16/24)\n", option_values.format_bps);
Josh Coalson's avatar
Josh Coalson committed
387
		}
388
389
390
391
		if(option_values.format_sample_rate >= 0) {
			if(!FLAC__format_sample_rate_is_valid(option_values.format_sample_rate))
				return usage_error("ERROR: invalid sample rate '%u', must be > 0 and <= %u\n", option_values.format_sample_rate, FLAC__MAX_SAMPLE_RATE);
		}
392
393
		if((option_values.force_raw_format?1:0) + (option_values.force_aiff_format?1:0) + (option_values.force_rf64_format?1:0) + (option_values.force_wave64_format?1:0) > 1)
			return usage_error("ERROR: only one of --force-raw-format/--force-aiff-format/--force-rf64-format/--force-wave64-format allowed\n");
394
395
396
397
398
399
400
401
402
403
404
405
406
407
		if(option_values.mode_decode) {
			if(!option_values.force_raw_format) {
				if(option_values.format_is_big_endian >= 0)
					return usage_error("ERROR: --endian only allowed with --force-raw-format\n");
				if(option_values.format_is_unsigned_samples >= 0)
					return usage_error("ERROR: --sign only allowed with --force-raw-format\n");
			}
			if(option_values.format_channels >= 0)
				return usage_error("ERROR: --channels not allowed with --decode\n");
			if(option_values.format_bps >= 0)
				return usage_error("ERROR: --bps not allowed with --decode\n");
			if(option_values.format_sample_rate >= 0)
				return usage_error("ERROR: --sample-rate not allowed with --decode\n");
		}
Josh Coalson's avatar
Josh Coalson committed
408

409
410
411
412
413
414
415
416
417
418
419
420
		if(option_values.ignore_chunk_sizes) {
			if(option_values.mode_decode)
				return usage_error("ERROR: --ignore-chunk-sizes only allowed for encoding\n");
			if(0 != option_values.sector_align)
				return usage_error("ERROR: --ignore-chunk-sizes not allowed with --sector-align\n");
			if(0 != option_values.until_specification)
				return usage_error("ERROR: --ignore-chunk-sizes not allowed with --until\n");
			if(0 != option_values.cue_specification)
				return usage_error("ERROR: --ignore-chunk-sizes not allowed with --cue\n");
			if(0 != option_values.cuesheet_filename)
				return usage_error("ERROR: --ignore-chunk-sizes not allowed with --cuesheet\n");
		}
421
422
423
		if(option_values.sector_align) {
			if(option_values.mode_decode)
				return usage_error("ERROR: --sector-align only allowed for encoding\n");
424
			if(0 != option_values.skip_specification)
425
				return usage_error("ERROR: --sector-align not allowed with --skip\n");
426
427
			if(0 != option_values.until_specification)
				return usage_error("ERROR: --sector-align not allowed with --until\n");
428
429
			if(0 != option_values.cue_specification)
				return usage_error("ERROR: --sector-align not allowed with --cue\n");
430
			if(option_values.format_channels >= 0 && option_values.format_channels != 2)
431
				return usage_error("ERROR: --sector-align can only be done with stereo input\n");
432
			if(option_values.format_bps >= 0 && option_values.format_bps != 16)
433
				return usage_error("ERROR: --sector-align can only be done with 16-bit samples\n");
434
			if(option_values.format_sample_rate >= 0 && option_values.format_sample_rate != 44100)
435
436
				return usage_error("ERROR: --sector-align can only be done with a sample rate of 44100\n");
		}
437
		if(option_values.replay_gain) {
438
439
			if(option_values.force_to_stdout)
				return usage_error("ERROR: --replay-gain not allowed with -c/--stdout\n");
440
441
442
443
			if(option_values.mode_decode)
				return usage_error("ERROR: --replay-gain only allowed for encoding\n");
			if(option_values.format_channels > 2)
				return usage_error("ERROR: --replay-gain can only be done with mono/stereo input\n");
Josh Coalson's avatar
Josh Coalson committed
444
			if(option_values.format_sample_rate >= 0 && !grabbag__replaygain_is_valid_sample_frequency(option_values.format_sample_rate))
445
				return usage_error("ERROR: invalid sample rate used with --replay-gain\n");
446
447
448
449
450
			/*
			 * We want to reserve padding space for the ReplayGain
			 * tags that we will set later, to avoid rewriting the
			 * whole file.
			 */
451
			if(
452
453
				(option_values.padding >= 0 && option_values.padding < (int)GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED) ||
				(option_values.padding < 0 && FLAC_ENCODE__DEFAULT_PADDING < (int)GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED)
454
			) {
455
				flac__utils_printf(stderr, 1, "NOTE: --replay-gain may leave a small PADDING block even with --no-padding\n");
Josh Coalson's avatar
Josh Coalson committed
456
				option_values.padding = GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED;
457
458
			}
			else {
Josh Coalson's avatar
Josh Coalson committed
459
				option_values.padding += GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED;
460
			}
461
		}
462
		if(option_values.num_files > 1 && option_values.cmdline_forced_outfilename) {
463
			return usage_error("ERROR: -o/--output-name cannot be used with multiple files\n");
464
465
		}
		if(option_values.cmdline_forced_outfilename && option_values.output_prefix) {
466
			return usage_error("ERROR: --output-prefix conflicts with -o/--output-name\n");
467
		}
468
469
470
		if(!option_values.mode_decode && 0 != option_values.cuesheet_filename && option_values.num_files > 1) {
			return usage_error("ERROR: --cuesheet cannot be used when encoding multiple files\n");
		}
471
472
473
474
		if(option_values.keep_foreign_metadata) {
			/* we're not going to try and support the re-creation of broken WAVE files */
			if(option_values.ignore_chunk_sizes)
				return usage_error("ERROR: using --keep-foreign-metadata cannot be used with --ignore-chunk-sizes\n");
475
476
477
478
			if(option_values.test_only)
				return usage_error("ERROR: --keep-foreign-metadata is not allowed in test mode\n");
			if(option_values.analyze)
				return usage_error("ERROR: --keep-foreign-metadata is not allowed in analyis mode\n");
479
480
			flac__utils_printf(stderr, 1, "NOTE: --keep-foreign-metadata is a new feature; make sure to test the output file before deleting the original.\n");
		}
Josh Coalson's avatar
Josh Coalson committed
481
	}
Josh Coalson's avatar
Josh Coalson committed
482

483
	flac__utils_printf(stderr, 2, "\n");
484
	flac__utils_printf(stderr, 2, "flac %s, Copyright (C) 2000-2009, 2011-2013  Josh Coalson & Xiph.Org Foundation\n", FLAC__VERSION_STRING);
485
486
487
	flac__utils_printf(stderr, 2, "flac comes with ABSOLUTELY NO WARRANTY.  This is free software, and you are\n");
	flac__utils_printf(stderr, 2, "welcome to redistribute it under certain conditions.  Type `flac' for details.\n\n");

488
	if(option_values.mode_decode) {
Josh Coalson's avatar
Josh Coalson committed
489
		FLAC__bool first = true;
Josh Coalson's avatar
Josh Coalson committed
490

491
		if(option_values.num_files == 0) {
492
			retval = decode_file("-");
493
		}
494
		else {
495
496
497
			unsigned i;
			if(option_values.num_files > 1)
				option_values.cmdline_forced_outfilename = 0;
498
			for(i = 0, retval = 0; i < option_values.num_files; i++) {
499
				if(0 == strcmp(option_values.filenames[i], "-") && !first)
500
					continue;
501
				retval |= decode_file(option_values.filenames[i]);
502
503
504
505
506
				first = false;
			}
		}
	}
	else { /* encode */
Josh Coalson's avatar
Josh Coalson committed
507
		FLAC__bool first = true;
508

509
510
511
		if(option_values.ignore_chunk_sizes)
			flac__utils_printf(stderr, 1, "INFO: Make sure you know what you're doing when using --ignore-chunk-sizes.\n      Improper use can cause flac to encode non-audio data as audio.\n");

512
		if(option_values.num_files == 0) {
513
			retval = encode_file("-", first, true);
514
		}
515
		else {
516
517
518
			unsigned i;
			if(option_values.num_files > 1)
				option_values.cmdline_forced_outfilename = 0;
519
			for(i = 0, retval = 0; i < option_values.num_files; i++) {
520
				if(0 == strcmp(option_values.filenames[i], "-") && !first)
521
					continue;
522
				retval |= encode_file(option_values.filenames[i], first, i == (option_values.num_files-1));
523
524
				first = false;
			}
525
526
			if(option_values.replay_gain && retval == 0) {
				float album_gain, album_peak;
Josh Coalson's avatar
Josh Coalson committed
527
				grabbag__replaygain_get_album(&album_gain, &album_peak);
528
529
				for(i = 0; i < option_values.num_files; i++) {
					const char *error, *outfilename = get_encoded_outfilename(option_values.filenames[i]);
530
					if(0 == outfilename) {
531
						flac__utils_printf(stderr, 1, "ERROR: filename too long: %s", option_values.filenames[i]);
532
533
						return 1;
					}
534
					if(0 != (error = grabbag__replaygain_store_to_file_album(outfilename, album_gain, album_peak, option_values.preserve_modtime))) {
535
						flac__utils_printf(stderr, 1, "%s: ERROR writing ReplayGain album tags (%s)\n", outfilename, error);
536
537
538
539
						retval = 1;
					}
				}
			}
540
541
542
543
		}
	}

	return retval;
Josh Coalson's avatar
Josh Coalson committed
544
545
}

546
FLAC__bool init_options(void)
Josh Coalson's avatar
Josh Coalson committed
547
{
548
549
	option_values.show_help = false;
	option_values.show_explain = false;
550
551
	option_values.mode_decode = false;
	option_values.verify = false;
552
	option_values.treat_warnings_as_errors = false;
553
	option_values.force_file_overwrite = false;
554
	option_values.continue_through_decode_errors = false;
555
556
557
558
559
	option_values.replaygain_synthesis_spec.apply = false;
	option_values.replaygain_synthesis_spec.use_album_gain = true;
	option_values.replaygain_synthesis_spec.limiter = RGSS_LIMIT__HARD;
	option_values.replaygain_synthesis_spec.noise_shaping = NOISE_SHAPING_LOW;
	option_values.replaygain_synthesis_spec.preamp = 0.0;
560
561
562
563
	option_values.lax = false;
	option_values.test_only = false;
	option_values.analyze = false;
	option_values.use_ogg = false;
564
565
	option_values.has_serial_number = false;
	option_values.serial_number = 0;
566
	option_values.force_to_stdout = false;
567
	option_values.force_raw_format = false;
568
	option_values.force_aiff_format = false;
Josh Coalson's avatar
Josh Coalson committed
569
	option_values.force_rf64_format = false;
570
	option_values.force_wave64_format = false;
571
	option_values.delete_input = false;
572
	option_values.preserve_modtime = true;
573
	option_values.keep_foreign_metadata = false;
574
	option_values.replay_gain = false;
575
	option_values.ignore_chunk_sizes = false;
576
	option_values.sector_align = false;
577
	option_values.utf8_convert = true;
578
579
580
581
	option_values.cmdline_forced_outfilename = 0;
	option_values.output_prefix = 0;
	option_values.aopts.do_residual_text = false;
	option_values.aopts.do_residual_gnuplot = false;
582
	option_values.padding = -1;
583
584
585
	option_values.num_compression_settings = 1;
	option_values.compression_settings[0].type = CST_COMPRESSION_LEVEL;
	option_values.compression_settings[0].value.t_unsigned = 5;
586
	option_values.skip_specification = 0;
587
	option_values.until_specification = 0;
588
	option_values.cue_specification = 0;
589
	option_values.format_is_big_endian = -1;
590
	option_values.format_is_unsigned_samples = -1;
591
592
593
	option_values.format_channels = -1;
	option_values.format_bps = -1;
	option_values.format_sample_rate = -1;
594
	option_values.format_input_size = (FLAC__off_t)(-1);
595
596
	option_values.requested_seek_points[0] = '\0';
	option_values.num_requested_seek_points = -1;
597
598
	option_values.cuesheet_filename = 0;
	option_values.cued_seekpoints = true;
Josh Coalson's avatar
Josh Coalson committed
599
	option_values.channel_map_none = false;
600
	option_values.error_on_compression_fail = false;
601
602
603

	option_values.num_files = 0;
	option_values.filenames = 0;
Josh Coalson's avatar
Josh Coalson committed
604
605
606

	if(0 == (option_values.vorbis_comment = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)))
		return false;
607
	option_values.num_pictures = 0;
Josh Coalson's avatar
Josh Coalson committed
608

609
610
611
	option_values.debug.disable_constant_subframes = false;
	option_values.debug.disable_fixed_subframes = false;
	option_values.debug.disable_verbatim_subframes = false;
Josh Coalson's avatar
Josh Coalson committed
612
	option_values.debug.do_md5 = true;
613

Josh Coalson's avatar
Josh Coalson committed
614
	return true;
615
616
}

617
int parse_options(int argc, char *argv[])
618
{
Josh Coalson's avatar
Josh Coalson committed
619
620
	int short_option;
	int option_index = 1;
621
	FLAC__bool had_error = false;
622
	const char *short_opts = "0123456789aA:b:cdefFhHl:mMo:pP:q:r:sS:tT:vVw";
623

Josh Coalson's avatar
Josh Coalson committed
624
	while ((short_option = share__getopt_long(argc, argv, short_opts, long_options_, &option_index)) != -1) {
Josh Coalson's avatar
Josh Coalson committed
625
626
		switch (short_option) {
			case 0: /* long option with no equivalent short option */
Josh Coalson's avatar
Josh Coalson committed
627
				had_error |= (parse_option(short_option, long_options_[option_index].name, share__optarg) != 0);
Josh Coalson's avatar
Josh Coalson committed
628
				break;
629
630
			case '?':
			case ':':
Josh Coalson's avatar
Josh Coalson committed
631
632
633
				had_error = true;
				break;
			default: /* short option */
Josh Coalson's avatar
Josh Coalson committed
634
				had_error |= (parse_option(short_option, 0, share__optarg) != 0);
Josh Coalson's avatar
Josh Coalson committed
635
636
637
				break;
		}
	}
638

639
640
641
	if(had_error) {
		return 1;
	}
642

Josh Coalson's avatar
Josh Coalson committed
643
	FLAC__ASSERT(share__optind <= argc);
644

Josh Coalson's avatar
Josh Coalson committed
645
	option_values.num_files = argc - share__optind;
646

647
648
	if(option_values.num_files > 0) {
		unsigned i = 0;
649
		if(0 == (option_values.filenames = malloc(sizeof(char*) * option_values.num_files)))
650
			die("out of memory allocating space for file names list");
Josh Coalson's avatar
Josh Coalson committed
651
652
		while(share__optind < argc)
			option_values.filenames[i++] = local_strdup(argv[share__optind++]);
653
	}
654
655
656
657
658
659

	return 0;
}

int parse_option(int short_option, const char *long_option, const char *option_argument)
{
660
	const char *violation;
661
662
663

	if(short_option == 0) {
		FLAC__ASSERT(0 != long_option);
664
		if(0 == strcmp(long_option, "totally-silent")) {
665
			flac__utils_verbosity_ = 0;
666
667
		}
		else if(0 == strcmp(long_option, "delete-input-file")) {
668
669
			option_values.delete_input = true;
		}
670
671
672
		else if(0 == strcmp(long_option, "preserve-modtime")) {
			option_values.preserve_modtime = true;
		}
673
674
675
		else if(0 == strcmp(long_option, "keep-foreign-metadata")) {
			option_values.keep_foreign_metadata = true;
		}
Josh Coalson's avatar
Josh Coalson committed
676
		else if(0 == strcmp(long_option, "output-prefix")) {
677
678
679
			FLAC__ASSERT(0 != option_argument);
			option_values.output_prefix = option_argument;
		}
Josh Coalson's avatar
Josh Coalson committed
680
		else if(0 == strcmp(long_option, "skip")) {
681
			FLAC__ASSERT(0 != option_argument);
682
			option_values.skip_specification = option_argument;
683
		}
684
685
686
687
		else if(0 == strcmp(long_option, "until")) {
			FLAC__ASSERT(0 != option_argument);
			option_values.until_specification = option_argument;
		}
Josh Coalson's avatar
Josh Coalson committed
688
689
		else if(0 == strcmp(long_option, "input-size")) {
			FLAC__ASSERT(0 != option_argument);
Josh Coalson's avatar
Josh Coalson committed
690
691
			{
				char *end;
Erik de Castro Lopo's avatar
Erik de Castro Lopo committed
692
693
				FLAC__int64 ix;
				ix = strtoll(option_argument, &end, 10);
Josh Coalson's avatar
Josh Coalson committed
694
695
				if(0 == strlen(option_argument) || *end)
					return usage_error("ERROR: --%s must be a number\n", long_option);
696
697
				option_values.format_input_size = (FLAC__off_t)ix;
				if(option_values.format_input_size != ix) /* check if FLAC__off_t is smaller than long long */
Josh Coalson's avatar
Josh Coalson committed
698
					return usage_error("ERROR: --%s too large; this build of flac does not support filesizes over 2GB\n", long_option);
Josh Coalson's avatar
Josh Coalson committed
699
700
701
				if(option_values.format_input_size <= 0)
					return usage_error("ERROR: --%s must be > 0\n", long_option);
			}
Josh Coalson's avatar
Josh Coalson committed
702
		}
703
704
705
706
		else if(0 == strcmp(long_option, "cue")) {
			FLAC__ASSERT(0 != option_argument);
			option_values.cue_specification = option_argument;
		}
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
		else if(0 == strcmp(long_option, "apply-replaygain-which-is-not-lossless")) {
			option_values.replaygain_synthesis_spec.apply = true;
			if (0 != option_argument) {
				char *p;
				option_values.replaygain_synthesis_spec.limiter = RGSS_LIMIT__NONE;
				option_values.replaygain_synthesis_spec.noise_shaping = NOISE_SHAPING_NONE;
				option_values.replaygain_synthesis_spec.preamp = strtod(option_argument, &p);
				for ( ; *p; p++) {
					if (*p == 'a')
						option_values.replaygain_synthesis_spec.use_album_gain = true;
					else if (*p == 't')
						option_values.replaygain_synthesis_spec.use_album_gain = false;
					else if (*p == 'l')
						option_values.replaygain_synthesis_spec.limiter = RGSS_LIMIT__PEAK;
					else if (*p == 'L')
						option_values.replaygain_synthesis_spec.limiter = RGSS_LIMIT__HARD;
					else if (*p == 'n' && p[1] >= '0' && p[1] <= '3') {
						option_values.replaygain_synthesis_spec.noise_shaping = p[1] - '0';
						p++;
					}
					else
						return usage_error("ERROR: bad specification string \"%s\" for --%s\n", option_argument, long_option);
				}
			}
		}
732
733
734
735
736
		else if(0 == strcmp(long_option, "channel-map")) {
			if (0 == option_argument || strcmp(option_argument, "none"))
				return usage_error("ERROR: only --channel-map=none currently supported\n");
			option_values.channel_map_none = true;
		}
737
738
739
740
		else if(0 == strcmp(long_option, "cuesheet")) {
			FLAC__ASSERT(0 != option_argument);
			option_values.cuesheet_filename = option_argument;
		}
741
742
743
744
745
746
747
748
749
		else if(0 == strcmp(long_option, "picture")) {
			const unsigned max_pictures = sizeof(option_values.pictures)/sizeof(option_values.pictures[0]);
			FLAC__ASSERT(0 != option_argument);
			if(option_values.num_pictures >= max_pictures)
				return usage_error("ERROR: too many --picture arguments, only %u allowed\n", max_pictures);
			if(0 == (option_values.pictures[option_values.num_pictures] = grabbag__picture_parse_specification(option_argument, &violation)))
				return usage_error("ERROR: (--picture) %s\n", violation);
			option_values.num_pictures++;
		}
750
751
		else if(0 == strcmp(long_option, "tag-from-file")) {
			FLAC__ASSERT(0 != option_argument);
752
			if(!flac__vorbiscomment_add(option_values.vorbis_comment, option_argument, /*value_from_file=*/true, /*raw=*/!option_values.utf8_convert, &violation))
753
754
				return usage_error("ERROR: (--tag-from-file) %s\n", violation);
		}
755
756
757
		else if(0 == strcmp(long_option, "no-cued-seekpoints")) {
			option_values.cued_seekpoints = false;
		}
758
759
760
		else if(0 == strcmp(long_option, "force-raw-format")) {
			option_values.force_raw_format = true;
		}
761
762
763
		else if(0 == strcmp(long_option, "force-aiff-format")) {
			option_values.force_aiff_format = true;
		}
Josh Coalson's avatar
Josh Coalson committed
764
765
766
		else if(0 == strcmp(long_option, "force-rf64-format")) {
			option_values.force_rf64_format = true;
		}
767
768
		else if(0 == strcmp(long_option, "force-wave64-format")) {
			option_values.force_wave64_format = true;
769
		}
Josh Coalson's avatar
Josh Coalson committed
770
		else if(0 == strcmp(long_option, "lax")) {
771
772
			option_values.lax = true;
		}
773
774
775
		else if(0 == strcmp(long_option, "replay-gain")) {
			option_values.replay_gain = true;
		}
776
777
778
		else if(0 == strcmp(long_option, "ignore-chunk-sizes")) {
			option_values.ignore_chunk_sizes = true;
		}
Josh Coalson's avatar
Josh Coalson committed
779
		else if(0 == strcmp(long_option, "sector-align")) {
Josh Coalson's avatar
Josh Coalson committed
780
781
			flac__utils_printf(stderr, 1, "WARNING: --sector-align is DEPRECATED and may not exist in future versions of flac.\n");
			flac__utils_printf(stderr, 1, "         shntool provides similar functionality\n");
782
783
			option_values.sector_align = true;
		}
784
#if FLAC__HAS_OGG
Josh Coalson's avatar
Josh Coalson committed
785
		else if(0 == strcmp(long_option, "ogg")) {
786
787
			option_values.use_ogg = true;
		}
788
789
790
791
		else if(0 == strcmp(long_option, "serial-number")) {
			option_values.has_serial_number = true;
			option_values.serial_number = atol(option_argument);
		}
792
#endif
Josh Coalson's avatar
Josh Coalson committed
793
		else if(0 == strcmp(long_option, "endian")) {
794
795
796
797
798
			FLAC__ASSERT(0 != option_argument);
			if(0 == strncmp(option_argument, "big", strlen(option_argument)))
				option_values.format_is_big_endian = true;
			else if(0 == strncmp(option_argument, "little", strlen(option_argument)))
				option_values.format_is_big_endian = false;
Josh Coalson's avatar
Josh Coalson committed
799
			else
800
801
				return usage_error("ERROR: argument to --endian must be \"big\" or \"little\"\n");
		}
Josh Coalson's avatar
Josh Coalson committed
802
		else if(0 == strcmp(long_option, "channels")) {
803
804
805
			FLAC__ASSERT(0 != option_argument);
			option_values.format_channels = atoi(option_argument);
		}
Josh Coalson's avatar
Josh Coalson committed
806
		else if(0 == strcmp(long_option, "bps")) {
807
808
809
			FLAC__ASSERT(0 != option_argument);
			option_values.format_bps = atoi(option_argument);
		}
Josh Coalson's avatar
Josh Coalson committed
810
		else if(0 == strcmp(long_option, "sample-rate")) {
811
812
813
			FLAC__ASSERT(0 != option_argument);
			option_values.format_sample_rate = atoi(option_argument);
		}
Josh Coalson's avatar
Josh Coalson committed
814
		else if(0 == strcmp(long_option, "sign")) {
815
816
817
818
819
			FLAC__ASSERT(0 != option_argument);
			if(0 == strncmp(option_argument, "signed", strlen(option_argument)))
				option_values.format_is_unsigned_samples = false;
			else if(0 == strncmp(option_argument, "unsigned", strlen(option_argument)))
				option_values.format_is_unsigned_samples = true;
Josh Coalson's avatar
Josh Coalson committed
820
			else
821
822
				return usage_error("ERROR: argument to --sign must be \"signed\" or \"unsigned\"\n");
		}
823
		else if(0 == strcmp(long_option, "residual-gnuplot")) {
824
825
			option_values.aopts.do_residual_gnuplot = true;
		}
Josh Coalson's avatar
Josh Coalson committed
826
		else if(0 == strcmp(long_option, "residual-text")) {
827
828
829
830
831
			option_values.aopts.do_residual_text = true;
		}
		/*
		 * negatives
		 */
832
833
834
		else if(0 == strcmp(long_option, "no-preserve-modtime")) {
			option_values.preserve_modtime = false;
		}
Josh Coalson's avatar
Josh Coalson committed
835
		else if(0 == strcmp(long_option, "no-decode-through-errors")) {
836
837
			option_values.continue_through_decode_errors = false;
		}
Josh Coalson's avatar
Josh Coalson committed
838
		else if(0 == strcmp(long_option, "no-silent")) {
839
			flac__utils_verbosity_ = 2;
840
		}
841
842
843
		else if(0 == strcmp(long_option, "no-force")) {
			option_values.force_file_overwrite = false;
		}
Josh Coalson's avatar
Josh Coalson committed
844
		else if(0 == strcmp(long_option, "no-seektable")) {
845
846
847
			option_values.num_requested_seek_points = 0;
			option_values.requested_seek_points[0] = '\0';
		}
Josh Coalson's avatar
Josh Coalson committed
848
		else if(0 == strcmp(long_option, "no-delete-input-file")) {
849
850
			option_values.delete_input = false;
		}
851
852
853
		else if(0 == strcmp(long_option, "no-keep-foreign-metadata")) {
			option_values.keep_foreign_metadata = false;
		}
854
855
856
		else if(0 == strcmp(long_option, "no-replay-gain")) {
			option_values.replay_gain = false;
		}
857
858
859
		else if(0 == strcmp(long_option, "no-ignore-chunk-sizes")) {
			option_values.ignore_chunk_sizes = false;
		}
Josh Coalson's avatar
Josh Coalson committed
860
		else if(0 == strcmp(long_option, "no-sector-align")) {
861
862
			option_values.sector_align = false;
		}
863
864
865
		else if(0 == strcmp(long_option, "no-utf8-convert")) {
			option_values.utf8_convert = false;
		}
Josh Coalson's avatar
Josh Coalson committed
866
		else if(0 == strcmp(long_option, "no-lax")) {
867
868
			option_values.lax = false;
		}
869
#if FLAC__HAS_OGG
Josh Coalson's avatar
Josh Coalson committed
870
		else if(0 == strcmp(long_option, "no-ogg")) {
871
872
873
			option_values.use_ogg = false;
		}
#endif
Josh Coalson's avatar
Josh Coalson committed
874
		else if(0 == strcmp(long_option, "no-exhaustive-model-search")) {
875
			add_compression_setting_bool(CST_DO_EXHAUSTIVE_MODEL_SEARCH, false);
876
		}
Josh Coalson's avatar
Josh Coalson committed
877
		else if(0 == strcmp(long_option, "no-mid-side")) {
878
879
			add_compression_setting_bool(CST_DO_MID_SIDE, false);
			add_compression_setting_bool(CST_LOOSE_MID_SIDE, false);