Commit 03a5a69e authored by Josh Coalson's avatar Josh Coalson
Browse files

add --cue option to flac, and tests and documentation

parent ba56c9e3
......@@ -420,7 +420,7 @@
<TT>--serial-number=#</TT>
</TD>
<TD>
When used with --ogg, specifies the serial number to use for the first Ogg FLAC stream, which is then incremented for each additional stream. When encoding and no serial number is given, <TT><B>flac</B></TT> uses a random number for the first stream, then increments it for each additional stream. When decoding and no number is given, <TT><B>flac</B></TT> uses the serial number of the first page.<P>
When used with --ogg, specifies the serial number to use for the first Ogg FLAC stream, which is then incremented for each additional stream. When encoding and no serial number is given, <TT><B>flac</B></TT> uses a random number for the first stream, then increments it for each additional stream. When decoding and no number is given, <TT><B>flac</B></TT> uses the serial number of the first page.
</TD>
</TR>
</TABLE>
......@@ -461,6 +461,20 @@
<A NAME="decoding_options"><FONT SIZE="+1"><B>Decoding Options</B></FONT></A>
</TD>
</TR>
<TR>
<TD NOWRAP ALIGN="RIGHT" VALIGN="TOP" BGCOLOR="#F4F4CC">
<TT>--cue=[#.#][-[#.#]]</TT>
</TD>
<TD>
Set the beginning and ending cuepoints to decode. The optional first <TT>#.#</TT> is the track and index point at which decoding will start; the default is the beginning of the stream. The optional second <TT>#.#</TT> is the track and index point at which decoding will end; the default is the end of the stream. If the seekpoint does not exist, the closest one before it (for the start point) or after it (for the end point) will be used. If those don't exist, the start of the stream (for the start point) or end of the stream (for the end point) will be used. The cuepoints are merely translated into sample numbers then used as --skip and --until.<P>
Examples:<P>
<TT>--cue=-</TT> : decode the entire stream<P>
<TT>--cue=4.1</TT> : decode from track 4, index 1 to the end of the stream<P>
<TT>--cue=4.1-</TT> : decode from track 4, index 1 to the end of the stream<P>
<TT>--cue=-4.1</TT> : decode from the beginning of the stream up to, but not including, track 4, index 1<P>
<TT>--cue=2.1-2.4</TT> : decode from track 2, index 1, up to, but not including, track 2, index 4<P>
</TD>
</TR>
<TR>
<TD NOWRAP ALIGN="RIGHT" VALIGN="TOP" BGCOLOR="#F4F4CC">
<TT>-F</TT>,<BR><TT>--decode-through-errors</TT>
......
......@@ -284,6 +284,25 @@
<refsect2>
<title>Decoding Options</title>
<variablelist>
<varlistentry>
<term><option>--cue=[<replaceable>#.#</replaceable>][-[<replaceable>#.#</replaceable>]]</option></term>
<listitem>
<para>Set the beginning and ending cuepoints to decode.
The optional first #.# is the track and index point at
which decoding will start; the default is the beginning
of the stream. The optional second #.# is the track
and index point at which decoding will end; the default
is the end of the stream. If the seekpoint does not
exist, the closest one before it (for the start point)
or after it (for the end point) will be used. If those
don't exist, the start of the stream (for the start
point) or end of the stream (for the end point) will be
used. The cuepoints are merely translated into sample
numbers then used as --skip and --until.</para>
</listitem>
</varlistentry>
<variablelist>
<varlistentry>
<term><option>-F</option>, <option>--decode-through-errors</option>
......
......@@ -60,6 +60,7 @@ typedef struct {
analysis_options aopts;
utils__SkipUntilSpecification *skip_specification;
utils__SkipUntilSpecification *until_specification; /* a canonicalized value of 0 mean end-of-stream (i.e. --until=-0) */
utils__CueSpecification *cue_specification;
const char *inbasefilename;
const char *outfilename;
......@@ -104,13 +105,14 @@ static FLAC__bool is_big_endian_host_;
/*
* local routines
*/
static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool verbose, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, const char *infilename, const char *outfilename);
static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool verbose, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, const char *infilename, const char *outfilename);
static void DecoderSession_destroy(DecoderSession *d, FLAC__bool error_occurred);
static FLAC__bool DecoderSession_init_decoder(DecoderSession *d, decode_options_t decode_options, const char *infilename);
static FLAC__bool DecoderSession_process(DecoderSession *d);
static int DecoderSession_finish_ok(DecoderSession *d);
static int DecoderSession_finish_error(DecoderSession *d);
static FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec, const char *inbasefilename, unsigned sample_rate, FLAC__uint64 skip, FLAC__uint64 total_samples_in_input);
static FLAC__bool write_necessary_headers(DecoderSession *decoder_session);
static FLAC__bool write_little_endian_uint16(FILE *f, FLAC__uint16 val);
static FLAC__bool write_little_endian_uint32(FILE *f, FLAC__uint32 val);
static FLAC__bool write_big_endian_uint16(FILE *f, FLAC__uint16 val);
......@@ -153,6 +155,7 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo
aopts,
&options.common.skip_specification,
&options.common.until_specification,
options.common.has_cue_specification? &options.common.cue_specification : 0,
infilename,
outfilename
)
......@@ -189,6 +192,7 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool
aopts,
&options.common.skip_specification,
&options.common.until_specification,
options.common.has_cue_specification? &options.common.cue_specification : 0,
infilename,
outfilename
)
......@@ -228,6 +232,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool
aopts,
&options.common.skip_specification,
&options.common.until_specification,
options.common.has_cue_specification? &options.common.cue_specification : 0,
infilename,
outfilename
)
......@@ -243,7 +248,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool
return DecoderSession_finish_ok(&decoder_session);
}
FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool verbose, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, const char *infilename, const char *outfilename)
FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool verbose, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, const char *infilename, const char *outfilename)
{
#ifdef FLAC__HAS_OGG
d->is_ogg = is_ogg;
......@@ -264,6 +269,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
d->aopts = aopts;
d->skip_specification = skip_specification;
d->until_specification = until_specification;
d->cue_specification = cue_specification;
d->inbasefilename = grabbag__file_get_basename(infilename);
d->outfilename = outfilename;
......@@ -331,6 +337,8 @@ FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, decode_o
OggFLAC__file_decoder_set_filename(decoder_session->decoder.ogg.file, infilename);
if(!decode_options.use_first_serial_number)
OggFLAC__file_decoder_set_serial_number(decoder_session->decoder.ogg.file, decode_options.serial_number);
if (0 != decoder_session->cue_specification)
OggFLAC__file_decoder_set_metadata_respond(decoder_session->decoder.ogg.file, FLAC__METADATA_TYPE_CUESHEET);
if (decoder_session->replaygain.spec.apply)
OggFLAC__file_decoder_set_metadata_respond(decoder_session->decoder.ogg.file, FLAC__METADATA_TYPE_VORBIS_COMMENT);
......@@ -363,6 +371,8 @@ FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, decode_o
FLAC__file_decoder_set_md5_checking(decoder_session->decoder.flac.file, true);
FLAC__file_decoder_set_filename(decoder_session->decoder.flac.file, infilename);
if (0 != decoder_session->cue_specification)
FLAC__file_decoder_set_metadata_respond(decoder_session->decoder.flac.file, FLAC__METADATA_TYPE_CUESHEET);
if (decoder_session->replaygain.spec.apply)
FLAC__file_decoder_set_metadata_respond(decoder_session->decoder.flac.file, FLAC__METADATA_TYPE_VORBIS_COMMENT);
/*
......@@ -415,6 +425,12 @@ FLAC__bool DecoderSession_process(DecoderSession *d)
if(d->abort_flag)
return false;
/* write the WAVE/AIFF headers if necessary */
if(!write_necessary_headers(d)) {
d->abort_flag = true;
return false;
}
if(d->skip_specification->value.samples > 0) {
const FLAC__uint64 skip = (FLAC__uint64)d->skip_specification->value.samples;
......@@ -599,6 +615,122 @@ FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec,
return true;
}
FLAC__bool write_necessary_headers(DecoderSession *decoder_session)
{
/* write the WAVE/AIFF headers if necessary */
if(!decoder_session->analysis_mode && !decoder_session->test_only && (decoder_session->is_wave_out || decoder_session->is_aiff_out)) {
const char *fmt_desc = decoder_session->is_wave_out? "WAVE" : "AIFF";
FLAC__uint64 data_size = decoder_session->total_samples * decoder_session->channels * ((decoder_session->bps+7)/8);
if(decoder_session->total_samples == 0) {
if(decoder_session->fout == stdout) {
fprintf(stderr, "%s: WARNING, don't have accurate sample count available for %s header.\n", decoder_session->inbasefilename, fmt_desc);
fprintf(stderr, " Generated %s file will have a data chunk size of 0. Try\n", fmt_desc);
fprintf(stderr, " decoding directly to a file instead.\n");
}
else {
decoder_session->wave_chunk_size_fixup.needs_fixup = true;
}
}
if(data_size >= 0xFFFFFFDC) {
fprintf(stderr, "%s: ERROR: stream is too big to fit in a single %s file chunk\n", decoder_session->inbasefilename, fmt_desc);
return false;
}
if(decoder_session->is_wave_out) {
if(flac__utils_fwrite("RIFF", 1, 4, decoder_session->fout) != 4)
return false;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.riff_offset = ftell(decoder_session->fout);
if(!write_little_endian_uint32(decoder_session->fout, (FLAC__uint32)(data_size+36))) /* filesize-8 */
return false;
if(flac__utils_fwrite("WAVEfmt ", 1, 8, decoder_session->fout) != 8)
return false;
if(flac__utils_fwrite("\020\000\000\000", 1, 4, decoder_session->fout) != 4) /* chunk size = 16 */
return false;
if(flac__utils_fwrite("\001\000", 1, 2, decoder_session->fout) != 2) /* compression code == 1 */
return false;
if(!write_little_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->channels)))
return false;
if(!write_little_endian_uint32(decoder_session->fout, decoder_session->sample_rate))
return false;
if(!write_little_endian_uint32(decoder_session->fout, decoder_session->sample_rate * decoder_session->channels * ((decoder_session->bps+7) / 8))) /* @@@ or is it (sample_rate*channels*bps) / 8 ??? */
return false;
if(!write_little_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->channels * ((decoder_session->bps+7) / 8)))) /* block align */
return false;
if(!write_little_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->bps))) /* bits per sample */
return false;
if(flac__utils_fwrite("data", 1, 4, decoder_session->fout) != 4)
return false;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.data_offset = ftell(decoder_session->fout);
if(!write_little_endian_uint32(decoder_session->fout, (FLAC__uint32)data_size)) /* data size */
return false;
}
else {
const FLAC__uint32 aligned_data_size = (FLAC__uint32)((data_size+1) & (~1U));
if(flac__utils_fwrite("FORM", 1, 4, decoder_session->fout) != 4)
return false;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.riff_offset = ftell(decoder_session->fout);
if(!write_big_endian_uint32(decoder_session->fout, (FLAC__uint32)(aligned_data_size+46))) /* filesize-8 */
return false;
if(flac__utils_fwrite("AIFFCOMM", 1, 8, decoder_session->fout) != 8)
return false;
if(flac__utils_fwrite("\000\000\000\022", 1, 4, decoder_session->fout) != 4) /* chunk size = 18 */
return false;
if(!write_big_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->channels)))
return false;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.frames_offset = ftell(decoder_session->fout);
if(!write_big_endian_uint32(decoder_session->fout, (FLAC__uint32)decoder_session->total_samples))
return false;
if(!write_big_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->bps)))
return false;
if(!write_sane_extended(decoder_session->fout, decoder_session->sample_rate))
return false;
if(flac__utils_fwrite("SSND", 1, 4, decoder_session->fout) != 4)
return false;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.data_offset = ftell(decoder_session->fout);
if(!write_big_endian_uint32(decoder_session->fout, (FLAC__uint32)data_size+8)) /* data size */
return false;
if(!write_big_endian_uint32(decoder_session->fout, 0/*offset*/))
return false;
if(!write_big_endian_uint32(decoder_session->fout, 0/*block_size*/))
return false;
}
}
return true;
}
FLAC__bool write_little_endian_uint16(FILE *f, FLAC__uint16 val)
{
FLAC__byte *b = (FLAC__byte*)(&val);
......@@ -923,6 +1055,7 @@ void metadata_callback(const void *decoder, const FLAC__StreamMetadata *metadata
decoder_session->abort_flag = true;
return;
}
FLAC__ASSERT(skip == 0 || 0 == decoder_session->cue_specification);
decoder_session->total_samples = metadata->data.stream_info.total_samples - skip;
/* note that we use metadata->data.stream_info.total_samples instead of decoder_session->total_samples */
......@@ -933,126 +1066,40 @@ void metadata_callback(const void *decoder, const FLAC__StreamMetadata *metadata
FLAC__ASSERT(decoder_session->until_specification->value.samples >= 0);
until = (FLAC__uint64)decoder_session->until_specification->value.samples;
if(until > 0)
if(until > 0) {
FLAC__ASSERT(decoder_session->total_samples != 0);
FLAC__ASSERT(0 == decoder_session->cue_specification);
decoder_session->total_samples -= (metadata->data.stream_info.total_samples - until);
}
if(decoder_session->bps != 8 && decoder_session->bps != 16 && decoder_session->bps != 24) {
fprintf(stderr, "%s: ERROR: bits per sample is not 8/16/24\n", decoder_session->inbasefilename);
decoder_session->abort_flag = true;
return;
}
}
else if(metadata->type == FLAC__METADATA_TYPE_CUESHEET) {
/* remember, at this point, decoder_session->total_samples can be 0, meaning 'unknown' */
if(decoder_session->total_samples == 0) {
fprintf(stderr, "%s: ERROR can't use --cue when FLAC metadata has total sample count of 0\n", decoder_session->inbasefilename);
decoder_session->abort_flag = true;
return;
}
/* write the WAVE/AIFF headers if necessary */
if(!decoder_session->analysis_mode && !decoder_session->test_only && (decoder_session->is_wave_out || decoder_session->is_aiff_out)) {
const char *fmt_desc = decoder_session->is_wave_out? "WAVE" : "AIFF";
FLAC__uint64 data_size = decoder_session->total_samples * decoder_session->channels * ((decoder_session->bps+7)/8);
if(decoder_session->total_samples == 0) {
if(decoder_session->fout == stdout) {
fprintf(stderr, "%s: WARNING, don't have accurate sample count available for %s header.\n", decoder_session->inbasefilename, fmt_desc);
fprintf(stderr, " Generated %s file will have a data chunk size of 0. Try\n", fmt_desc);
fprintf(stderr, " decoding directly to a file instead.\n");
}
else {
decoder_session->wave_chunk_size_fixup.needs_fixup = true;
}
}
if(data_size >= 0xFFFFFFDC) {
fprintf(stderr, "%s: ERROR: stream is too big to fit in a single %s file chunk\n", decoder_session->inbasefilename, fmt_desc);
decoder_session->abort_flag = true;
return;
}
if(decoder_session->is_wave_out) {
if(flac__utils_fwrite("RIFF", 1, 4, decoder_session->fout) != 4)
decoder_session->abort_flag = true;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.riff_offset = ftell(decoder_session->fout);
if(!write_little_endian_uint32(decoder_session->fout, (FLAC__uint32)(data_size+36))) /* filesize-8 */
decoder_session->abort_flag = true;
if(flac__utils_fwrite("WAVEfmt ", 1, 8, decoder_session->fout) != 8)
decoder_session->abort_flag = true;
if(flac__utils_fwrite("\020\000\000\000", 1, 4, decoder_session->fout) != 4) /* chunk size = 16 */
decoder_session->abort_flag = true;
if(flac__utils_fwrite("\001\000", 1, 2, decoder_session->fout) != 2) /* compression code == 1 */
decoder_session->abort_flag = true;
if(!write_little_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->channels)))
decoder_session->abort_flag = true;
if(!write_little_endian_uint32(decoder_session->fout, decoder_session->sample_rate))
decoder_session->abort_flag = true;
if(!write_little_endian_uint32(decoder_session->fout, decoder_session->sample_rate * decoder_session->channels * ((decoder_session->bps+7) / 8))) /* @@@ or is it (sample_rate*channels*bps) / 8 ??? */
decoder_session->abort_flag = true;
if(!write_little_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->channels * ((decoder_session->bps+7) / 8)))) /* block align */
decoder_session->abort_flag = true;
if(!write_little_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->bps))) /* bits per sample */
decoder_session->abort_flag = true;
if(flac__utils_fwrite("data", 1, 4, decoder_session->fout) != 4)
decoder_session->abort_flag = true;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.data_offset = ftell(decoder_session->fout);
if(!write_little_endian_uint32(decoder_session->fout, (FLAC__uint32)data_size)) /* data size */
decoder_session->abort_flag = true;
}
else {
const FLAC__uint32 aligned_data_size = (FLAC__uint32)((data_size+1) & (~1U));
if(flac__utils_fwrite("FORM", 1, 4, decoder_session->fout) != 4)
decoder_session->abort_flag = true;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.riff_offset = ftell(decoder_session->fout);
if(!write_big_endian_uint32(decoder_session->fout, (FLAC__uint32)(aligned_data_size+46))) /* filesize-8 */
decoder_session->abort_flag = true;
if(flac__utils_fwrite("AIFFCOMM", 1, 8, decoder_session->fout) != 8)
decoder_session->abort_flag = true;
if(flac__utils_fwrite("\000\000\000\022", 1, 4, decoder_session->fout) != 4) /* chunk size = 18 */
decoder_session->abort_flag = true;
if(!write_big_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->channels)))
decoder_session->abort_flag = true;
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.frames_offset = ftell(decoder_session->fout);
if(!write_big_endian_uint32(decoder_session->fout, (FLAC__uint32)decoder_session->total_samples))
decoder_session->abort_flag = true;
if(!write_big_endian_uint16(decoder_session->fout, (FLAC__uint16)(decoder_session->bps)))
decoder_session->abort_flag = true;
if(!write_sane_extended(decoder_session->fout, decoder_session->sample_rate))
decoder_session->abort_flag = true;
if(flac__utils_fwrite("SSND", 1, 4, decoder_session->fout) != 4)
decoder_session->abort_flag = true;
flac__utils_canonicalize_cue_specification(decoder_session->cue_specification, &metadata->data.cue_sheet, decoder_session->total_samples, decoder_session->skip_specification, decoder_session->until_specification);
if(decoder_session->wave_chunk_size_fixup.needs_fixup)
decoder_session->wave_chunk_size_fixup.data_offset = ftell(decoder_session->fout);
FLAC__ASSERT(!decoder_session->skip_specification->is_relative);
FLAC__ASSERT(decoder_session->skip_specification->value_is_samples);
if(!write_big_endian_uint32(decoder_session->fout, (FLAC__uint32)data_size+8)) /* data size */
decoder_session->abort_flag = true;
FLAC__ASSERT(!decoder_session->until_specification->is_relative);
FLAC__ASSERT(decoder_session->until_specification->value_is_samples);
if(!write_big_endian_uint32(decoder_session->fout, 0/*offset*/))
decoder_session->abort_flag = true;
FLAC__ASSERT(decoder_session->skip_specification->value.samples >= 0);
FLAC__ASSERT(decoder_session->until_specification->value.samples >= 0);
FLAC__ASSERT((FLAC__uint64)decoder_session->until_specification->value.samples <= decoder_session->total_samples);
FLAC__ASSERT(decoder_session->skip_specification->value.samples <= decoder_session->until_specification->value.samples);
if(!write_big_endian_uint32(decoder_session->fout, 0/*block_size*/))
decoder_session->abort_flag = true;
}
}
decoder_session->total_samples = decoder_session->until_specification->value.samples - decoder_session->skip_specification->value.samples;
}
else if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
if (decoder_session->replaygain.spec.apply) {
......
......@@ -46,6 +46,8 @@ typedef struct {
#endif
utils__SkipUntilSpecification skip_specification;
utils__SkipUntilSpecification until_specification;
FLAC__bool has_cue_specification;
utils__CueSpecification cue_specification;
} decode_options_t;
/* used for AIFF also */
......
......@@ -105,6 +105,7 @@ static struct share__option long_options_[] = {
* decoding options
*/
{ "decode-through-errors", share__no_argument, 0, 'F' },
{ "cue" , share__required_argument, 0, 0 },
{ "apply-replaygain-which-is-not-lossless", share__optional_argument, 0, 0 }, /* undocumented */
/*
......@@ -230,6 +231,7 @@ static struct {
unsigned qlp_coeff_precision;
const char *skip_specification;
const char *until_specification;
const char *cue_specification;
int format_is_big_endian;
int format_is_unsigned_samples;
int format_channels;
......@@ -331,6 +333,8 @@ int do_it()
if(option_values.rice_parameter_search_dist < 0) {
option_values.rice_parameter_search_dist = 0;
}
if(0 != option_values.cue_specification)
return usage_error("ERROR: --cue is not allowed in test mode\n");
}
else {
if(option_values.test_only) {
......@@ -338,9 +342,14 @@ int do_it()
return usage_error("ERROR: --skip is not allowed in test mode\n");
if(0 != option_values.until_specification)
return usage_error("ERROR: --until is not allowed in test mode\n");
if(0 != option_values.cue_specification)
return usage_error("ERROR: --cue is not allowed in test mode\n");
}
}
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");
FLAC__ASSERT(option_values.blocksize >= 0 || option_values.mode_decode);
if(option_values.format_channels >= 0) {
......@@ -385,6 +394,8 @@ int do_it()
return usage_error("ERROR: --sector-align not allowed with --skip\n");
if(0 != option_values.until_specification)
return usage_error("ERROR: --sector-align not allowed with --until\n");
if(0 != option_values.cue_specification)
return usage_error("ERROR: --sector-align not allowed with --cue\n");
if(option_values.format_channels >= 0 && option_values.format_channels != 2)
return usage_error("ERROR: --sector-align can only be done with stereo input\n");
if(option_values.format_bps >= 0 && option_values.format_bps != 16)
......@@ -558,6 +569,7 @@ FLAC__bool init_options()
option_values.qlp_coeff_precision = 0;
option_values.skip_specification = 0;
option_values.until_specification = 0;
option_values.cue_specification = 0;
option_values.format_is_big_endian = -1;
option_values.format_is_unsigned_samples = -1;
option_values.format_channels = -1;
......@@ -647,6 +659,10 @@ int parse_option(int short_option, const char *long_option, const char *option_a
FLAC__ASSERT(0 != option_argument);
option_values.until_specification = option_argument;
}
else if(0 == strcmp(long_option, "cue")) {
FLAC__ASSERT(0 != option_argument);
option_values.cue_specification = option_argument;
}
else if(0 == strcmp(long_option, "apply-replaygain-which-is-not-lossless")) {
option_values.replaygain_synthesis_spec.apply = true;
if (0 != option_argument) {
......@@ -1148,6 +1164,7 @@ void show_help()
printf(" --residual-gnuplot Generate gnuplot files of residual distribution\n");
printf("decoding options:\n");
printf(" -F, --decode-through-errors Continue decoding through stream errors\n");
printf(" --cue=[#.#][-[#.#]] Set the beginning and ending cuepoints to decode\n");
printf("encoding options:\n");
printf(" -V, --verify Verify a correct encoding\n");
printf(" --lax Allow encoder to generate non-Subset files\n");
......@@ -1293,6 +1310,17 @@ void show_explain()
printf(" decoding to completion. Note that errors may\n");
printf(" cause the decoded audio to be missing some\n");
printf(" samples or have silent sections.\n");
printf(" --cue=[#.#][-[#.#]] Set the beginning and ending cuepoints to\n");
printf(" decode. The optional first #.# is the track and\n");
printf(" index point at which decoding will start; the\n");
printf(" default is the beginning of the stream. The\n");
printf(" optional second #.# is the track and index point\n");
printf(" at which decoding will end; the default is the\n");
printf(" end of the stream. If the seekpoint does not\n");
printf(" exist, the closest one before it (for the start\n");
printf(" point) or after it (for the end point) will be\n");
printf(" used. The cuepoints are merely translated into\n");
printf(" sample numbers then used as --skip and --until.\n");
printf("encoding options:\n");
printf(" -V, --verify Verify a correct encoding by decoding the\n");
printf(" output in parallel and comparing to the\n");
......@@ -1638,6 +1666,14 @@ int decode_file(const char *infilename)
if(0 == option_values.until_specification)
common_options.until_specification.is_relative = true;
if(option_values.cue_specification) {
if(!flac__utils_parse_cue_specification(option_values.cue_specification, &common_options.cue_specification))
return usage_error("ERROR: invalid value for --cue\n");
common_options.has_cue_specification = true;
}
else
common_options.has_cue_specification = false;
common_options.verbose = option_values.verbose;
common_options.continue_through_decode_errors = option_values.continue_through_decode_errors;
common_options.replaygain_synthesis_spec = option_values.replaygain_synthesis_spec;
......
......@@ -78,6 +78,61 @@ static FLAC__bool local__parse_timecode_(const char *s, double *value)
return true;
}
static FLAC__bool local__parse_cue_(const char *s, const char *end, unsigned *track, unsigned *index)
{
FLAC__bool got_track = false, got_index = false;
unsigned t = 0, i = 0;
char c;
while(end? s < end : *s != '\0') {
c = *s++;
if(c >= '0' && c <= '9') {
t = t * 10 + (c - '0');
got_track = true;
}
else if(c == '.')
break;
else
return false;
}
while(end? s < end : *s != '\0') {
c = *s++;
if(c >= '0' && c <= '9') {
i = i * 10 + (c - '0');
got_index = true;
}
else
return false;
}
*track = t;
*index = i;
return got_track && got_index;
}
/*
* @@@ this only works with sorted cuesheets (the spec strongly recommends but
* does not require sorted cuesheets). but if it's not sorted, picking a
* nearest cue point has no significance.
*/
static FLAC__uint64 local__find_closest_cue_(const FLAC__StreamMetadata_CueSheet *cuesheet, unsigned track, unsigned index, FLAC__uint64 total_samples, FLAC__bool look_forward)
{
int t, i;
if(look_forward) {
for(t = 0; t < (int)cuesheet->num_tracks; t++)
for(i = 0; i < (int)cuesheet->tracks[t].num_indices; i++)
if(cuesheet->tracks[t].number > track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number >= index))
return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset;
return total_samples;
}
else {
for(t = (int)cuesheet->num_tracks - 1; t >= 0; t--)
for(i = (int)cuesheet->tracks[t].num_indices - 1; i >= 0; i--)
if(cuesheet->tracks[t].number < track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number <= index))
return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset;
return 0;