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

This commit is contained in:
Josh Coalson 2004-07-17 00:23:17 +00:00
parent ba56c9e31a
commit 03a5a69e11
8 changed files with 562 additions and 121 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
/* 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;
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 */
decoder_session->abort_flag = true;
if(!write_big_endian_uint32(decoder_session->fout, 0/*offset*/))
decoder_session->abort_flag = true;
if(!write_big_endian_uint32(decoder_session->fout, 0/*block_size*/))
decoder_session->abort_flag = true;
}
}
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;
}
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);
FLAC__ASSERT(!decoder_session->skip_specification->is_relative);
FLAC__ASSERT(decoder_session->skip_specification->value_is_samples);
FLAC__ASSERT(!decoder_session->until_specification->is_relative);
FLAC__ASSERT(decoder_session->until_specification->value_is_samples);
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);
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) {

View File

@ -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 */

View File

@ -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;

View File

@ -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;
}
}
#ifdef FLAC__VALGRIND_TESTING
size_t flac__utils_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
@ -138,3 +193,61 @@ void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecifica
spec->value_is_samples = true;
}
}
FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec)
{
const char *start = s, *end = 0;
FLAC__ASSERT(0 != spec);
spec->has_start_point = spec->has_end_point = false;
s = strchr(s, '-');
if(0 != s) {
if(s == start)
start = 0;
end = s+1;
if(*end == '\0')
end = 0;
}
if(start) {
if(!local__parse_cue_(start, s, &spec->start_track, &spec->start_index))
return false;
spec->has_start_point = true;
}
if(end) {
if(!local__parse_cue_(end, 0, &spec->end_track, &spec->end_index))
return false;
spec->has_end_point = true;
}
return true;
}
void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *cue_spec, const FLAC__StreamMetadata_CueSheet *cuesheet, FLAC__uint64 total_samples, utils__SkipUntilSpecification *skip_spec, utils__SkipUntilSpecification *until_spec)
{
FLAC__ASSERT(0 != cue_spec);
FLAC__ASSERT(0 != cuesheet);
FLAC__ASSERT(0 != total_samples);
FLAC__ASSERT(0 != skip_spec);
FLAC__ASSERT(0 != until_spec);
skip_spec->is_relative = false;
skip_spec->value_is_samples = true;
until_spec->is_relative = false;
until_spec->value_is_samples = true;
if(cue_spec->has_start_point)
skip_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->start_track, cue_spec->start_index, total_samples, /*look_forward=*/false);
else
skip_spec->value.samples = 0;
if(cue_spec->has_end_point)
until_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->end_track, cue_spec->end_index, total_samples, /*look_forward=*/true);
else
until_spec->value.samples = total_samples;
}

View File

@ -20,6 +20,7 @@
#define flac__utils_h
#include "FLAC/ordinals.h"
#include "FLAC/format.h" /* for FLAC__StreamMetadata_CueSheet */
#include <stdio.h> /* for FILE */
typedef struct {
@ -31,6 +32,12 @@ typedef struct {
} value;
} utils__SkipUntilSpecification;
typedef struct {
FLAC__bool has_start_point, has_end_point;
unsigned start_track, start_index;
unsigned end_track, end_index;
} utils__CueSpecification;
#ifdef FLAC__VALGRIND_TESTING
size_t flac__utils_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
#else
@ -39,4 +46,7 @@ size_t flac__utils_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stre
FLAC__bool flac__utils_parse_skip_until_specification(const char *s, utils__SkipUntilSpecification *spec);
void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecification *spec, unsigned sample_rate);
FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec);
void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *cue_spec, const FLAC__StreamMetadata_CueSheet *cuesheet, FLAC__uint64 total_samples, utils__SkipUntilSpecification *skip_spec, utils__SkipUntilSpecification *until_spec);
#endif

View File

@ -157,10 +157,19 @@ dddie="die ERROR: creating files for --skip/--until tests"
dd if=master.raw ibs=1 count=50 of=50c.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=10 count=40 of=50c.skip10.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=11 count=39 of=50c.skip11.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 count=40 of=50c.until40.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=20 count=30 of=50c.skip20.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=30 count=20 of=50c.skip30.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=40 count=10 of=50c.skip40.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 count=10 of=50c.until10.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 count=20 of=50c.until20.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 count=30 of=50c.until30.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 count=39 of=50c.until39.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=10 count=30 of=50c.skip10.until40.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 count=40 of=50c.until40.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=10 count=20 of=50c.skip10.until30.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=10 count=29 of=50c.skip10.until39.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=10 count=30 of=50c.skip10.until40.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=20 count=10 of=50c.skip20.until30.raw 2>/dev/null || $dddie
dd if=master.raw ibs=1 skip=20 count=20 of=50c.skip20.until40.raw 2>/dev/null || $dddie
wav_eopt="--silent --force --verify --lax"
wav_dopt="--silent --force --decode"
@ -179,10 +188,19 @@ convert_to_wav ()
convert_to_wav 50c
convert_to_wav 50c.skip10
convert_to_wav 50c.skip11
convert_to_wav 50c.until40
convert_to_wav 50c.skip20
convert_to_wav 50c.skip30
convert_to_wav 50c.skip40
convert_to_wav 50c.until10
convert_to_wav 50c.until20
convert_to_wav 50c.until30
convert_to_wav 50c.until39
convert_to_wav 50c.skip10.until40
convert_to_wav 50c.until40
convert_to_wav 50c.skip10.until30
convert_to_wav 50c.skip10.until39
convert_to_wav 50c.skip10.until40
convert_to_wav 50c.skip20.until30
convert_to_wav 50c.skip20.until40
convert_to_aiff ()
{
@ -192,10 +210,19 @@ convert_to_aiff ()
convert_to_aiff 50c
convert_to_aiff 50c.skip10
convert_to_aiff 50c.skip11
convert_to_aiff 50c.until40
convert_to_aiff 50c.skip20
convert_to_aiff 50c.skip30
convert_to_aiff 50c.skip40
convert_to_aiff 50c.until10
convert_to_aiff 50c.until20
convert_to_aiff 50c.until30
convert_to_aiff 50c.until39
convert_to_aiff 50c.skip10.until40
convert_to_aiff 50c.until40
convert_to_aiff 50c.skip10.until30
convert_to_aiff 50c.skip10.until39
convert_to_aiff 50c.skip10.until40
convert_to_aiff 50c.skip20.until30
convert_to_aiff 50c.skip20.until40
test_skip_until ()
{
@ -482,6 +509,179 @@ if [ $has_ogg = "yes" ] ; then
test_skip_until aiff ogg
fi
############################################################################
# test --cue
############################################################################
#
# create the cue sheet
#
cuesheet=cuetest.cue
cat > $cuesheet << EOF
CATALOG 1234567890123
FILE "blah" WAVE
TRACK 01 AUDIO
INDEX 01 0
INDEX 02 10
INDEX 03 20
TRACK 02 AUDIO
INDEX 01 30
TRACK 04 AUDIO
INDEX 01 40
EOF
test_cue ()
{
in_fmt=$1
out_fmt=$2
[ "$in_fmt" = wav ] || [ "$in_fmt" = aiff ] || [ "$in_fmt" = raw ] || die "ERROR: internal error, bad 'in' format '$in_fmt'"
[ "$out_fmt" = flac ] || [ "$out_fmt" = ogg ] || die "ERROR: internal error, bad 'out' format '$out_fmt'"
if [ $in_fmt = raw ] ; then
eopt="$raw_eopt"
dopt="$raw_dopt"
else
eopt="$wav_eopt"
dopt="$wav_dopt"
fi
if [ $out_fmt = ogg ] ; then
eopt="--ogg $eopt"
fi
desc="($in_fmt<->$out_fmt)"
#
# for this we need just need just one FLAC file; --cue only works while decoding
#
run_flac $eopt --cuesheet=$cuesheet -o z50c.cue.$out_fmt 50c.$in_fmt || die "ERROR generating FLAC file $desc"
# To make it easy to translate from cue point to sample numbers, the
# file has a sample rate of 10 Hz and a cuesheet like so:
#
# TRACK 01, INDEX 01 : 0:00.00 -> sample 0
# TRACK 01, INDEX 02 : 0:01.00 -> sample 10
# TRACK 01, INDEX 03 : 0:02.00 -> sample 20
# TRACK 02, INDEX 01 : 0:03.00 -> sample 30
# TRACK 04, INDEX 01 : 0:04.00 -> sample 40
#
echo -n "testing --cue=- $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=- z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=- $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.0 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.0 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.0 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.0- $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.0- z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.0- $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.1 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.1 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.1 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.1- $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.1- z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.1- $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.2 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.2 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.skip10.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.2 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.2- $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.2- z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.skip10.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.2- $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.4 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.4 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.skip20.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.4 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.4- $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.4- z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.skip20.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.4- $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=-5.0 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=-5.0 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=-5.0 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=-4.1 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=-4.1 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.until40.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=-4.1 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=-3.1 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=-3.1 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.until40.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=-3.1 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=-1.4 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=-1.4 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.until30.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=-1.4 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.0-5.0 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.0-5.0 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.0-5.0 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.1-5.0 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.1-5.0 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.1-5.0 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.2-4.1 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.2-4.1 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.skip10.until40.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.2-4.1 $desc"
rm -f z50c.cue.$in_fmt
echo OK
echo -n "testing --cue=1.4-2.0 $desc... "
run_flac $dopt -o z50c.cue.$in_fmt --cue=1.4-2.0 z50c.cue.$out_fmt || die "ERROR decoding FLAC file $desc"
cmp 50c.skip20.until30.$in_fmt z50c.cue.$in_fmt || die "ERROR: file mismatch for --cue=1.4-2.0 $desc"
rm -f z50c.cue.$in_fmt
echo OK
rm -f z50c.cue.$out_fmt
}
test_cue raw flac
test_cue wav flac
test_cue aiff flac
if [ $has_ogg = "yes" ] ; then
test_cue raw ogg
test_cue wav ogg
test_cue aiff ogg
fi
############################################################################
# test 'fixup' code that happens when a FLAC file with total_samples == 0
# in the STREAMINFO block is converted to WAVE or AIFF, requiring the