diff --git a/doc/html/documentation.html b/doc/html/documentation.html index 311b1fe0..f8a8a373 100644 --- a/doc/html/documentation.html +++ b/doc/html/documentation.html @@ -420,7 +420,7 @@ --serial-number=# - 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, flac uses a random number for the first stream, then increments it for each additional stream. When decoding and no number is given, flac uses the serial number of the first page.

+ 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, flac uses a random number for the first stream, then increments it for each additional stream. When decoding and no number is given, flac uses the serial number of the first page. @@ -461,6 +461,20 @@ Decoding Options + + + --cue=[#.#][-[#.#]] + + + 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.

+ Examples:

+ --cue=- : decode the entire stream

+ --cue=4.1 : decode from track 4, index 1 to the end of the stream

+ --cue=4.1- : decode from track 4, index 1 to the end of the stream

+ --cue=-4.1 : decode from the beginning of the stream up to, but not including, track 4, index 1

+ --cue=2.1-2.4 : decode from track 2, index 1, up to, but not including, track 2, index 4

+ + -F,
--decode-through-errors diff --git a/man/flac.sgml b/man/flac.sgml index f1741a99..796cfdbf 100644 --- a/man/flac.sgml +++ b/man/flac.sgml @@ -284,6 +284,25 @@ Decoding Options + + + + + 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. + + + , diff --git a/src/flac/decode.c b/src/flac/decode.c index 7f0ec89a..0ec61340 100644 --- a/src/flac/decode.c +++ b/src/flac/decode.c @@ -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) { diff --git a/src/flac/decode.h b/src/flac/decode.h index 69e0d3fe..2e455f7a 100644 --- a/src/flac/decode.h +++ b/src/flac/decode.h @@ -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 */ diff --git a/src/flac/main.c b/src/flac/main.c index 787ad05c..93bde52d 100644 --- a/src/flac/main.c +++ b/src/flac/main.c @@ -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; diff --git a/src/flac/utils.c b/src/flac/utils.c index cad97433..ff82ab70 100644 --- a/src/flac/utils.c +++ b/src/flac/utils.c @@ -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; +} diff --git a/src/flac/utils.h b/src/flac/utils.h index 62956dbd..5cca12c3 100644 --- a/src/flac/utils.h +++ b/src/flac/utils.h @@ -20,6 +20,7 @@ #define flac__utils_h #include "FLAC/ordinals.h" +#include "FLAC/format.h" /* for FLAC__StreamMetadata_CueSheet */ #include /* 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 diff --git a/test/test_flac.sh b/test/test_flac.sh index 48841073..a52fbcb0 100755 --- a/test/test_flac.sh +++ b/test/test_flac.sh @@ -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