diff --git a/src/flac/Makefile.am b/src/flac/Makefile.am index 3ebd8e32..cea8a523 100644 --- a/src/flac/Makefile.am +++ b/src/flac/Makefile.am @@ -28,6 +28,7 @@ flac_SOURCES = \ analyze.c \ decode.c \ encode.c \ + foreign_metadata.c \ main.c \ local_string_utils.c \ utils.c \ @@ -35,6 +36,7 @@ flac_SOURCES = \ analyze.h \ decode.h \ encode.h \ + foreign_metadata.h \ local_string_utils.h \ utils.h \ vorbiscomment.h diff --git a/src/flac/Makefile.lite b/src/flac/Makefile.lite index 7d1795fe..9e8a0cc9 100644 --- a/src/flac/Makefile.lite +++ b/src/flac/Makefile.lite @@ -36,6 +36,7 @@ SRCS_C = \ analyze.c \ decode.c \ encode.c \ + foreign_metadata.c \ local_string_utils.c \ main.c \ utils.c \ diff --git a/src/flac/decode.c b/src/flac/decode.c index 30a4b176..1847c59b 100644 --- a/src/flac/decode.c +++ b/src/flac/decode.c @@ -45,6 +45,8 @@ typedef struct { #if FLAC__HAS_OGG FLAC__bool is_ogg; + FLAC__bool use_first_serial_number; + long serial_number; #endif FLAC__bool is_aiff_out; @@ -94,6 +96,8 @@ typedef struct { FLAC__StreamDecoder *decoder; FILE *fout; + + foreign_metadata_t *foreign_metadata; /* NULL unless --keep-foreign-metadata requested */ } DecoderSession; @@ -103,9 +107,9 @@ static FLAC__bool is_big_endian_host_; /* * local routines */ -static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, 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 FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool use_first_serial_number, long serial_number, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, 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, foreign_metadata_t *foreign_metadata, 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_init_decoder(DecoderSession *d, const char *infilename); static FLAC__bool DecoderSession_process(DecoderSession *d); static int DecoderSession_finish_ok(DecoderSession *d); static int DecoderSession_finish_error(DecoderSession *d); @@ -137,8 +141,12 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo &decoder_session, #if FLAC__HAS_OGG options.common.is_ogg, + options.common.use_first_serial_number, + options.common.serial_number, #else /*is_ogg=*/false, + /*use_first_serial_number=*/false, + /*serial_number=*/0, #endif /*is_aiff_out=*/true, /*is_wave_out=*/false, @@ -151,13 +159,14 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo &options.common.skip_specification, &options.common.until_specification, options.common.has_cue_specification? &options.common.cue_specification : 0, + options.foreign_metadata, infilename, outfilename ) ) return 1; - if(!DecoderSession_init_decoder(&decoder_session, options.common, infilename)) + if(!DecoderSession_init_decoder(&decoder_session, infilename)) return DecoderSession_finish_error(&decoder_session); if(!DecoderSession_process(&decoder_session)) @@ -175,8 +184,12 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool &decoder_session, #if FLAC__HAS_OGG options.common.is_ogg, + options.common.use_first_serial_number, + options.common.serial_number, #else /*is_ogg=*/false, + /*use_first_serial_number=*/false, + /*serial_number=*/0, #endif /*is_aiff_out=*/false, /*is_wave_out=*/true, @@ -189,13 +202,14 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool &options.common.skip_specification, &options.common.until_specification, options.common.has_cue_specification? &options.common.cue_specification : 0, + options.foreign_metadata, infilename, outfilename ) ) return 1; - if(!DecoderSession_init_decoder(&decoder_session, options.common, infilename)) + if(!DecoderSession_init_decoder(&decoder_session, infilename)) return DecoderSession_finish_error(&decoder_session); if(!DecoderSession_process(&decoder_session)) @@ -216,8 +230,12 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool &decoder_session, #if FLAC__HAS_OGG options.common.is_ogg, + options.common.use_first_serial_number, + options.common.serial_number, #else /*is_ogg=*/false, + /*use_first_serial_number=*/false, + /*serial_number=*/0, #endif /*is_aiff_out=*/false, /*is_wave_out=*/false, @@ -230,13 +248,14 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool &options.common.skip_specification, &options.common.until_specification, options.common.has_cue_specification? &options.common.cue_specification : 0, + /*foreign_metadata=*/NULL, infilename, outfilename ) ) return 1; - if(!DecoderSession_init_decoder(&decoder_session, options.common, infilename)) + if(!DecoderSession_init_decoder(&decoder_session, infilename)) return DecoderSession_finish_error(&decoder_session); if(!DecoderSession_process(&decoder_session)) @@ -245,10 +264,12 @@ 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 is_aiff_out, FLAC__bool is_wave_out, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, 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) +FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool use_first_serial_number, long serial_number, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, 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, foreign_metadata_t *foreign_metadata, const char *infilename, const char *outfilename) { #if FLAC__HAS_OGG d->is_ogg = is_ogg; + d->use_first_serial_number = use_first_serial_number; + d->serial_number = serial_number; #else (void)is_ogg; #endif @@ -294,6 +315,8 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__ d->fout = 0; /* initialized with an open file later if necessary */ + d->foreign_metadata = foreign_metadata; + FLAC__ASSERT(!(d->test_only && d->analysis_mode)); if(!d->test_only) { @@ -324,13 +347,21 @@ void DecoderSession_destroy(DecoderSession *d, FLAC__bool error_occurred) } } -FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, decode_options_t decode_options, const char *infilename) +FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, const char *infilename) { FLAC__StreamDecoderInitStatus init_status; FLAC__uint32 test = 1; is_big_endian_host_ = (*((FLAC__byte*)(&test)))? false : true; + if(decoder_session->foreign_metadata) { + const char *error; + if(!flac__foreign_metadata_read_from_flac(decoder_session->foreign_metadata, infilename, &error)) { + flac__utils_printf(stderr, 1, "%s: ERROR reading foreign metadata: %s\n", decoder_session->inbasefilename, error); + return false; + } + } + decoder_session->decoder = FLAC__stream_decoder_new(); if(0 == decoder_session->decoder) { @@ -346,8 +377,8 @@ FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, decode_o #if FLAC__HAS_OGG if(decoder_session->is_ogg) { - if(!decode_options.use_first_serial_number) - FLAC__stream_decoder_set_ogg_serial_number(decoder_session->decoder, decode_options.serial_number); + if(!decoder_session->use_first_serial_number) + FLAC__stream_decoder_set_ogg_serial_number(decoder_session->decoder, decoder_session->serial_number); init_status = FLAC__stream_decoder_init_ogg_file(decoder_session->decoder, strcmp(infilename, "-")? infilename : 0, write_callback, metadata_callback, error_callback, /*client_data=*/decoder_session); } else diff --git a/src/flac/decode.h b/src/flac/decode.h index 2d3c3aef..2ab9deb6 100644 --- a/src/flac/decode.h +++ b/src/flac/decode.h @@ -19,13 +19,15 @@ #ifndef flac__decode_h #define flac__decode_h +#if HAVE_CONFIG_H +# include +#endif + #include "analyze.h" +#include "foreign_metadata.h" #include "utils.h" #include "share/replaygain_synthesis.h" -#ifdef HAVE_CONFIG_H -#include -#endif typedef struct { FLAC__bool apply; @@ -54,6 +56,7 @@ typedef struct { /* used for AIFF also */ typedef struct { decode_options_t common; + foreign_metadata_t *foreign_metadata; /* NULL unless --keep-foreign-metadata requested */ } wav_decode_options_t; typedef struct { diff --git a/src/flac/encode.c b/src/flac/encode.c index 5da51391..973af63c 100644 --- a/src/flac/encode.c +++ b/src/flac/encode.c @@ -38,7 +38,7 @@ #include /* for floor() */ #include /* for FILE etc. */ #include /* for malloc */ -#include /* for strcmp(), strerror( */ +#include /* for strcmp(), strerror() */ #include "FLAC/all.h" #include "share/grabbag.h" #include "encode.h" @@ -63,6 +63,7 @@ typedef struct { FLAC__bool is_stdout; FLAC__bool outputfile_opened; /* true if we successfully opened the output file and we want it to be deleted if there is an error */ const char *inbasefilename; + const char *infilename; const char *outfilename; FLAC__uint64 skip; @@ -123,9 +124,9 @@ extern FLAC__bool FLAC__stream_encoder_set_do_md5(FLAC__StreamEncoder *encoder, */ static FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg, FLAC__bool verify, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FILE *infile, const char *infilename, const char *outfilename); static void EncoderSession_destroy(EncoderSession *e); -static int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int info_align_zero); +static int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int info_align_zero, foreign_metadata_t *foreign_metadata); static int EncoderSession_finish_error(EncoderSession *e); -static FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, FLAC__uint32 channel_mask, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data); +static FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, FLAC__uint32 channel_mask, unsigned channels, unsigned bps, unsigned sample_rate, const foreign_metadata_t *foreign_metadata, FLACDecoderData *flac_decoder_data); static FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const buffer[], unsigned samples); static FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, FLAC__StreamMetadata *cuesheet, EncoderSession *e); static FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec, const char *inbasefilename, unsigned sample_rate, FLAC__uint64 skip, FLAC__uint64 total_samples_in_input); @@ -199,6 +200,14 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con channel_map[i] = i; } + if(options.foreign_metadata) { + const char *error; + if(!flac__foreign_metadata_read_from_aiff(options.foreign_metadata, infilename, &error)) { + flac__utils_printf(stderr, 1, "%s: ERROR reading foreign metadata: %s\n", encoder_session.inbasefilename, error); + return EncoderSession_finish_error(&encoder_session); + } + } + /* lookahead[] already has "FORMxxxxAIFF", do sub-chunks */ while(1) { @@ -438,7 +447,7 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con /* +54 for the size of the AIFF headers; this is just an estimate for the progress indicator and doesn't need to be exact */ encoder_session.unencoded_size= encoder_session.total_samples_to_encode*bytes_per_frame+54; - if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, channels, bps-shift, sample_rate, /*flac_decoder_data=*/0)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, channels, bps-shift, sample_rate, options.foreign_metadata, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* first do any samples in the reservoir */ @@ -572,7 +581,7 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con if(encoder_session.treat_warnings_as_errors) return EncoderSession_finish_error(&encoder_session); } - else { + else if(!options.foreign_metadata) { flac__utils_printf(stderr, 1, "%s: WARNING: skipping unknown chunk '%s'\n", encoder_session.inbasefilename, chunk_id); if(encoder_session.treat_warnings_as_errors) return EncoderSession_finish_error(&encoder_session); @@ -598,7 +607,7 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con return EncoderSession_finish_error(&encoder_session); } - return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero); + return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero, options.foreign_metadata); } int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, const char *outfilename, const FLAC__byte *lookahead, unsigned lookahead_length, wav_encode_options_t options) @@ -643,6 +652,14 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con channel_map[i] = i; } + if(options.foreign_metadata) { + const char *error; + if(!flac__foreign_metadata_read_from_wave(options.foreign_metadata, infilename, &error)) { + flac__utils_printf(stderr, 1, "%s: ERROR reading foreign metadata: %s\n", encoder_session.inbasefilename, error); + return EncoderSession_finish_error(&encoder_session); + } + } + /* * lookahead[] already has "RIFFxxxxWAVE", do sub-chunks */ @@ -667,7 +684,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con * 4 byte: avg bytes per sec * 2 byte: block align * 2 byte: bits per sample (not necessarily all significant) - * WAVEFORMAT adds + * WAVEFORMATEX adds * 2 byte: extension size in bytes (usually 0 for WAVEFORMATEX and 22 for WAVEFORMATEXTENSIBLE with PCM) * WAVEFORMATEXTENSIBLE adds * 2 byte: valid bits per sample @@ -986,7 +1003,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con /* +44 for the size of the WAV headers; this is just an estimate for the progress indicator and doesn't need to be exact */ encoder_session.unencoded_size = encoder_session.total_samples_to_encode * bytes_per_wide_sample + 44; - if(!EncoderSession_init_encoder(&encoder_session, options.common, channel_mask, channels, bps-shift, sample_rate, /*flac_decoder_data=*/0)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, channel_mask, channels, bps-shift, sample_rate, options.foreign_metadata, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* @@ -1133,7 +1150,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con FLAC__ASSERT(0); } } - else { + else if(!options.foreign_metadata) { flac__utils_printf(stderr, 1, "%s: WARNING: skipping unknown sub-chunk '%c%c%c%c'\n", encoder_session.inbasefilename, (char)(xx&255), (char)((xx>>8)&255), (char)((xx>>16)&255), (char)(xx>>24)); if(encoder_session.treat_warnings_as_errors) return EncoderSession_finish_error(&encoder_session); @@ -1153,7 +1170,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con } } - return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero); + return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero, options.foreign_metadata); } int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, const char *outfilename, const FLAC__byte *lookahead, unsigned lookahead_length, raw_encode_options_t options) @@ -1254,7 +1271,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con } } - if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, options.channels, options.bps, options.sample_rate, /*flac_decoder_data=*/0)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, options.channels, options.bps, options.sample_rate, /*foreign_metadata=*/0, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* @@ -1421,7 +1438,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con } } - return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero); + return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero, /*foreign_metadata=*/0); } int flac__encode_flac(FILE *infile, off_t infilesize, const char *infilename, const char *outfilename, const FLAC__byte *lookahead, unsigned lookahead_length, flac_encode_options_t options, FLAC__bool input_is_ogg) @@ -1537,7 +1554,7 @@ int flac__encode_flac(FILE *infile, off_t infilesize, const char *infilename, co encoder_session.unencoded_size = decoder_data.filesize; /* (channel mask will get copied over from the source VORBIS_COMMENT if it exists) */ - if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, decoder_data.metadata_blocks[0]->data.stream_info.channels, decoder_data.metadata_blocks[0]->data.stream_info.bits_per_sample, decoder_data.metadata_blocks[0]->data.stream_info.sample_rate, &decoder_data)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, decoder_data.metadata_blocks[0]->data.stream_info.channels, decoder_data.metadata_blocks[0]->data.stream_info.bits_per_sample, decoder_data.metadata_blocks[0]->data.stream_info.sample_rate, /*foreign_metadata=*/0, &decoder_data)) goto fubar2; /*@@@ yuck */ /* @@ -1577,7 +1594,7 @@ int flac__encode_flac(FILE *infile, off_t infilesize, const char *infilename, co } FLAC__stream_decoder_delete(decoder); - retval = EncoderSession_finish_ok(&encoder_session, -1, -1); + retval = EncoderSession_finish_ok(&encoder_session, -1, -1, /*foreign_metadata=*/0); /* have to wail until encoder is completely finished before deleting because of the final step of writing the seekpoint offsets */ for(i = 0; i < decoder_data.num_metadata_blocks; i++) FLAC__metadata_object_delete(decoder_data.metadata_blocks[i]); @@ -1623,6 +1640,7 @@ FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg, FLAC_ e->outputfile_opened = false; e->inbasefilename = grabbag__file_get_basename(infilename); + e->infilename = infilename; e->outfilename = outfilename; e->skip = 0; /* filled in later after the sample_rate is known */ @@ -1668,7 +1686,7 @@ void EncoderSession_destroy(EncoderSession *e) } } -int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int info_align_zero) +int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int info_align_zero, foreign_metadata_t *foreign_metadata) { FLAC__StreamEncoderState fse_state = FLAC__STREAM_ENCODER_OK; int ret = 0; @@ -1703,6 +1721,15 @@ int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int info_a } } + /*@@@@@@ should this go here or somewhere else? */ + if(ret == 0 && foreign_metadata) { + const char *error; + if(!flac__foreign_metadata_write_to_flac(foreign_metadata, e->infilename, e->outfilename, &error)) { + flac__utils_printf(stderr, 1, "%s: ERROR: updating foreign metadata in FLAC file: %s\n", e->inbasefilename, error); + ret = 1; + } + } + EncoderSession_destroy(e); return ret; @@ -1726,18 +1753,65 @@ int EncoderSession_finish_error(EncoderSession *e) return 1; } -FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, FLAC__uint32 channel_mask, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data) +typedef struct { + unsigned num_metadata; + FLAC__bool *needs_delete; + FLAC__StreamMetadata **metadata; + FLAC__StreamMetadata *cuesheet; /* always needs to be deleted */ +} static_metadata_t; + +static void static_metadata_init(static_metadata_t *m) { - unsigned num_metadata, i; - FLAC__StreamMetadata padding, *cuesheet = 0; - FLAC__StreamMetadata *static_metadata[4+64]; /* MAGIC +64 is for pictures metadata in options.pictures */ - FLAC__StreamMetadata **metadata = static_metadata; + m->num_metadata = 0; + m->needs_delete = 0; + m->metadata = 0; + m->cuesheet = 0; +} + +static void static_metadata_clear(static_metadata_t *m) +{ + unsigned i; + for(i = 0; i < m->num_metadata; i++) + if(m->needs_delete[i]) + FLAC__metadata_object_delete(m->metadata[i]); + if(m->metadata) + free(m->metadata); + if(m->needs_delete) + free(m->needs_delete); + if(m->cuesheet) + FLAC__metadata_object_delete(m->cuesheet); + static_metadata_init(m); +} + +static FLAC__bool static_metadata_append(static_metadata_t *m, FLAC__StreamMetadata *d, FLAC__bool needs_delete) +{ + void *x; + if(0 == (x = realloc(m->metadata, sizeof(*m->metadata)*m->num_metadata+1))) + return false; + m->metadata = (FLAC__StreamMetadata**)x; + if(0 == (x = realloc(m->needs_delete, sizeof(*m->needs_delete)*m->num_metadata+1))) + return false; + m->needs_delete = (FLAC__bool*)x; + m->metadata[m->num_metadata] = d; + m->needs_delete[m->num_metadata] = needs_delete; + m->num_metadata++; + return true; +} + +FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, FLAC__uint32 channel_mask, unsigned channels, unsigned bps, unsigned sample_rate, const foreign_metadata_t *foreign_metadata, FLACDecoderData *flac_decoder_data) +{ + FLAC__StreamMetadata padding; + FLAC__StreamMetadata **metadata = 0; + static_metadata_t static_metadata; + unsigned num_metadata = 0, i; FLAC__StreamEncoderInitStatus init_status; const FLAC__bool is_cdda = (channels == 1 || channels == 2) && (bps == 16) && (sample_rate == 44100); char apodizations[2000]; FLAC__ASSERT(sizeof(options.pictures)/sizeof(options.pictures[0]) <= 64); + static_metadata_init(&static_metadata); + e->replay_gain = options.replay_gain; e->channels = channels; e->bits_per_sample = bps; @@ -1762,16 +1836,16 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio } } - if(!parse_cuesheet(&cuesheet, options.cuesheet_filename, e->inbasefilename, is_cdda, e->total_samples_to_encode, e->treat_warnings_as_errors)) + if(!parse_cuesheet(&static_metadata.cuesheet, options.cuesheet_filename, e->inbasefilename, is_cdda, e->total_samples_to_encode, e->treat_warnings_as_errors)) return false; - if(!convert_to_seek_table_template(options.requested_seek_points, options.num_requested_seek_points, options.cued_seekpoints? cuesheet : 0, e)) { + if(!convert_to_seek_table_template(options.requested_seek_points, options.num_requested_seek_points, options.cued_seekpoints? static_metadata.cuesheet : 0, e)) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for seek table\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } + /* build metadata */ if(flac_decoder_data) { /* * we're encoding from FLAC so we will use the FLAC file's @@ -1786,8 +1860,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio FLAC__StreamMetadata *pic = FLAC__metadata_object_clone(options.pictures[i]); if(0 == pic) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for PICTURE block\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks++] = pic; @@ -1811,8 +1884,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio (void) flac__utils_get_channel_mask_tag(flac_decoder_data->metadata_blocks[i], &channel_mask); flac__utils_printf(stderr, 1, "%s: WARNING, replacing tags from input FLAC file with those given on the command-line\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } FLAC__metadata_object_delete(flac_decoder_data->metadata_blocks[i]); @@ -1827,8 +1899,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio FLAC__StreamMetadata *vc = FLAC__metadata_object_clone(options.vorbis_comment); if(0 == vc || (channel_mask && !flac__utils_set_channel_mask_tag(vc, channel_mask))) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for VORBIS_COMMENT block\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } for(i = flac_decoder_data->num_metadata_blocks; i > 1; i--) @@ -1847,13 +1918,12 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio for(i = 0, j = 0; i < flac_decoder_data->num_metadata_blocks; i++) { FLAC__bool existing_cuesheet_is_bad = false; /* check if existing cuesheet matches the input audio */ - if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_CUESHEET && 0 == cuesheet) { + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_CUESHEET && 0 == static_metadata.cuesheet) { const FLAC__StreamMetadata_CueSheet *cs = &flac_decoder_data->metadata_blocks[i]->data.cue_sheet; if(e->total_samples_to_encode == 0) { flac__utils_printf(stderr, 1, "%s: WARNING, cuesheet in input FLAC file cannot be kept if input size is not known, dropping it...\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } existing_cuesheet_is_bad = true; @@ -1861,18 +1931,17 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio else if(e->total_samples_to_encode != cs->tracks[cs->num_tracks-1].offset) { flac__utils_printf(stderr, 1, "%s: WARNING, lead-out offset of cuesheet in input FLAC file does not match input length, dropping existing cuesheet...\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } existing_cuesheet_is_bad = true; } } - if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_CUESHEET && (existing_cuesheet_is_bad || 0 != cuesheet)) { - if(0 != cuesheet) { + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_CUESHEET && (existing_cuesheet_is_bad || 0 != static_metadata.cuesheet)) { + if(0 != static_metadata.cuesheet) { flac__utils_printf(stderr, 1, "%s: WARNING, replacing cuesheet in input FLAC file with the one given on the command-line\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } } @@ -1883,13 +1952,12 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio flac_decoder_data->metadata_blocks[j++] = flac_decoder_data->metadata_blocks[i]; } flac_decoder_data->num_metadata_blocks = j; - if(0 != cuesheet && flac_decoder_data->num_metadata_blocks < sizeof(flac_decoder_data->metadata_blocks)/sizeof(flac_decoder_data->metadata_blocks[0])) { + if(0 != static_metadata.cuesheet && flac_decoder_data->num_metadata_blocks < sizeof(flac_decoder_data->metadata_blocks)/sizeof(flac_decoder_data->metadata_blocks[0])) { /* prepend ours */ - FLAC__StreamMetadata *cs = FLAC__metadata_object_clone(cuesheet); + FLAC__StreamMetadata *cs = FLAC__metadata_object_clone(static_metadata.cuesheet); if(0 == cs) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for CUESHEET block\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } for(i = flac_decoder_data->num_metadata_blocks; i > 1; i--) @@ -1920,8 +1988,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio if(options.num_requested_seek_points > 0) { flac__utils_printf(stderr, 1, "%s: WARNING, replacing seektable in input FLAC file with the one given on the command-line\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } } @@ -1930,8 +1997,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio else { flac__utils_printf(stderr, 1, "%s: WARNING, can't use existing seektable in input FLAC since the input size is changing or unknown, dropping existing SEEKTABLE block...\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } } @@ -1948,8 +2014,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio FLAC__StreamMetadata *st = FLAC__metadata_object_clone(e->seek_table_template); if(0 == st) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for SEEKTABLE block\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } for(i = flac_decoder_data->num_metadata_blocks; i > 1; i--) @@ -1990,8 +2055,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); if(0 == flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks]) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for PADDING block\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks]->is_last = false; /* the encoder will set this for us */ @@ -2008,30 +2072,43 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio * we're not encoding from FLAC so we will build the metadata * from scratch */ - num_metadata = 0; if(e->seek_table_template->data.seek_table.num_points > 0) { e->seek_table_template->is_last = false; /* the encoder will set this for us */ - metadata[num_metadata++] = e->seek_table_template; + static_metadata_append(&static_metadata, e->seek_table_template, /*needs_delete=*/false); } - if(0 != cuesheet) - metadata[num_metadata++] = cuesheet; + if(0 != static_metadata.cuesheet) + static_metadata_append(&static_metadata, static_metadata.cuesheet, /*needs_delete=*/false); if(channel_mask) { if(!flac__utils_set_channel_mask_tag(options.vorbis_comment, channel_mask)) { flac__utils_printf(stderr, 1, "%s: ERROR adding channel mask tag\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } } - metadata[num_metadata++] = options.vorbis_comment; + static_metadata_append(&static_metadata, options.vorbis_comment, /*needs_delete=*/false); for(i = 0; i < options.num_pictures; i++) - metadata[num_metadata++] = options.pictures[i]; + static_metadata_append(&static_metadata, options.pictures[i], /*needs_delete=*/false); + if(foreign_metadata) { + for(i = 0; i < foreign_metadata->num_blocks; i++) { + FLAC__StreamMetadata *p = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); + if(!p) { + flac__utils_printf(stderr, 1, "%s: ERROR: out of memory\n", e->inbasefilename); + static_metadata_clear(&static_metadata); + return false; + } + static_metadata_append(&static_metadata, p, /*needs_delete=*/true); + static_metadata.metadata[static_metadata.num_metadata-1]->length = FLAC__STREAM_METADATA_APPLICATION_ID_LEN/8 + foreign_metadata->blocks[i].size; +fprintf(stderr,"@@@@@@ add PADDING=%u\n",static_metadata.metadata[static_metadata.num_metadata-1]->length); + } + } if(options.padding != 0) { padding.is_last = false; /* the encoder will set this for us */ padding.type = FLAC__METADATA_TYPE_PADDING; padding.length = (unsigned)(options.padding>0? options.padding : (e->total_samples_to_encode / e->sample_rate < 20*60? FLAC_ENCODE__DEFAULT_PADDING : FLAC_ENCODE__DEFAULT_PADDING*8)); - metadata[num_metadata++] = &padding; + static_metadata_append(&static_metadata, &padding, /*needs_delete=*/false); } + metadata = static_metadata.metadata; + num_metadata = static_metadata.num_metadata; } /* check for a few things that have not already been checked. the @@ -2040,8 +2117,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio * up front to give a better error message. */ if(!verify_metadata(e, metadata, num_metadata)) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } @@ -2068,8 +2144,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio case CST_APODIZATION: if(strlen(apodizations)+strlen(options.compression_settings[i].value.t_string)+2 >= sizeof(apodizations)) { flac__utils_printf(stderr, 1, "%s: ERROR: too many apodization functions requested\n", e->inbasefilename); - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } else { @@ -2114,8 +2189,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio if(!options.debug.do_md5) { flac__utils_printf(stderr, 1, "%s: WARNING, MD5 computation disabled, resulting file will not have MD5 sum\n", e->inbasefilename); if(e->treat_warnings_as_errors) { - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } FLAC__stream_encoder_set_do_md5(e->encoder, false); @@ -2137,8 +2211,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio print_error_with_init_status(e, "ERROR initializing encoder", init_status); if(FLAC__stream_encoder_get_state(e->encoder) != FLAC__STREAM_ENCODER_IO_ERROR) e->outputfile_opened = true; - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return false; } else @@ -2149,8 +2222,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio (FLAC__stream_encoder_get_do_exhaustive_model_search(e->encoder) || FLAC__stream_encoder_get_do_qlp_coeff_prec_search(e->encoder))? 0x0f : 0x3f; - if(0 != cuesheet) - FLAC__metadata_object_delete(cuesheet); + static_metadata_clear(&static_metadata); return true; } diff --git a/src/flac/encode.h b/src/flac/encode.h index 1394c89a..13ee7d39 100644 --- a/src/flac/encode.h +++ b/src/flac/encode.h @@ -19,18 +19,14 @@ #ifndef flac__encode_h #define flac__encode_h -/* needed because of off_t */ #if HAVE_CONFIG_H # include #endif #include "FLAC/metadata.h" +#include "foreign_metadata.h" #include "utils.h" -#ifdef HAVE_CONFIG_H -#include -#endif - extern const int FLAC_ENCODE__DEFAULT_PADDING; typedef enum { @@ -101,6 +97,7 @@ typedef struct { typedef struct { encode_options_t common; + foreign_metadata_t *foreign_metadata; /* NULL unless --keep-foreign-metadata requested */ } wav_encode_options_t; typedef struct { diff --git a/src/flac/foreign_metadata.c b/src/flac/foreign_metadata.c new file mode 100644 index 00000000..8883f966 --- /dev/null +++ b/src/flac/foreign_metadata.c @@ -0,0 +1,365 @@ +/* flac - Command-line FLAC encoder/decoder + * Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if HAVE_CONFIG_H +# include +#endif + +#if defined _MSC_VER || defined __MINGW32__ +#include /* for off_t */ +#if _MSC_VER <= 1600 /* @@@ [2G limit] */ +#define fseeko fseek +#define ftello ftell +#endif +#endif +#include /* for FILE etc. */ +#include /* for calloc() etc. */ +#include /* for memcmp() etc. */ +#include "FLAC/assert.h" +#include "FLAC/metadata.h" +#include "foreign_metadata.h" + +#ifdef min +#undef min +#endif +#define min(x,y) ((x)<(y)?(x):(y)) + + +static const char *FLAC__FOREIGN_METADATA_APPLICATION_ID = "FFMB";/*@@@@@@ settle on an ID */ + +static FLAC__uint32 unpack32be_(const FLAC__byte *b) +{ + return ((FLAC__uint32)b[0]<<24) + ((FLAC__uint32)b[1]<<16) + ((FLAC__uint32)b[2]<<8) + (FLAC__uint32)b[3]; +} + +static FLAC__uint32 unpack32le_(const FLAC__byte *b) +{ + return (FLAC__uint32)b[0] + ((FLAC__uint32)b[1]<<8) + ((FLAC__uint32)b[2]<<16) + ((FLAC__uint32)b[3]<<24); +} + +static FLAC__bool append_block_(foreign_metadata_t *fm, off_t offset, FLAC__uint32 size, const char **error) +{ + foreign_block_t *fb = realloc(fm->blocks, sizeof(foreign_block_t) * (fm->num_blocks+1)); + if(fb) { + fb[fm->num_blocks].offset = offset; + fb[fm->num_blocks].size = size; + fm->num_blocks++; + fm->blocks = fb; + return true; + } + if(error) *error = "out of memory"; + return false; +} + +static FLAC__bool read_from_aiff_(foreign_metadata_t *fm, FILE *f, const char **error) +{ + FLAC__byte buffer[12]; + off_t offset, eof_offset; + if((offset = ftello(f)) < 0) { + if(error) *error = "ftello() error (001)"; + return false; + } + if(fread(buffer, 1, 12, f) < 12 || memcmp(buffer, "FORM", 4) || (memcmp(buffer+8, "AIFF", 4) && memcmp(buffer+8, "AIFC", 4))) { + if(error) *error = "unsupported FORM layout (002)"; + return false; + } + if(!append_block_(fm, offset, 12, error)) + return false; + eof_offset = 8 + unpack32be_(buffer+4); + while(!feof(f)) { + FLAC__uint32 size, ssnd_offset_size = 0; + if((offset = ftello(f)) < 0) { + if(error) *error = "ftello() error (003)"; + return false; + } + if((size = fread(buffer, 1, 8, f)) < 8) { + if(size == 0 && feof(f)) + break; + if(error) *error = "invalid AIFF file (004)"; + return false; + } + size = unpack32be_(buffer+4); + /* check if pad byte needed */ + if(size & 1) + size++; + if(!memcmp(buffer, "COMM", 4)) { + if(fm->format_block) { + if(error) *error = "invalid AIFF file: multiple \"COMM\" chunks (005)"; + return false; + } + fm->format_block = fm->num_blocks; + } + else if(!memcmp(buffer, "SSND", 4)) { + if(fm->audio_block) { + if(error) *error = "invalid AIFF file: multiple \"SSND\" chunks (006)"; + return false; + } + fm->audio_block = fm->num_blocks; + /* read #offset bytes */ + if(fread(buffer+8, 1, 4, f) < 4) { + if(error) *error = "invalid AIFF file (007)"; + return false; + } + ssnd_offset_size = unpack32be_(buffer+8); + if(fseeko(f, -4, SEEK_CUR) < 0) { + if(error) *error = "invalid AIFF file: seek error (008)"; + return false; + } + } + if(!append_block_(fm, offset, 8 + (memcmp(buffer, "SSND", 4)? size : 8 + ssnd_offset_size), error)) + return false; +fprintf(stderr,"@@@@@@ chunk=%c%c%c%c offset=%d size=%d\n",buffer[0],buffer[1],buffer[2],buffer[3],(int)offset,8+(int)size); + if(fseeko(f, size, SEEK_CUR) < 0) { + if(error) *error = "invalid AIFF file: seek error (009)"; + return false; + } + } + if(eof_offset != ftello(f)) { + if(error) *error = "invalid AIFF file: unexpected EOF (010)"; + return false; + } + if(!fm->format_block) { + if(error) *error = "invalid AIFF file: missing \"COMM\" chunk (011)"; + return false; + } + if(!fm->audio_block) { + if(error) *error = "invalid AIFF file: missing \"SSND\" chunk (012)"; + return false; + } + return true; +} + +static FLAC__bool read_from_wave_(foreign_metadata_t *fm, FILE *f, const char **error) +{ + FLAC__byte buffer[12]; + off_t offset, eof_offset; + if((offset = ftello(f)) < 0) { + if(error) *error = "ftello() error (001)"; + return false; + } + if(fread(buffer, 1, 12, f) < 12 || memcmp(buffer, "RIFF", 4) || memcmp(buffer+8, "WAVE", 4)) { + if(error) *error = "unsupported RIFF layout (002)"; + return false; + } + if(!append_block_(fm, offset, 12, error)) + return false; + eof_offset = 8 + unpack32le_(buffer+4); +fprintf(stderr,"@@@@@@ off=%d eof=%d\n",(int)offset,(int)eof_offset); + while(!feof(f)) { + FLAC__uint32 size; + if((offset = ftello(f)) < 0) { + if(error) *error = "ftello() error (003)"; + return false; + } + if((size = fread(buffer, 1, 8, f)) < 8) { + if(size == 0 && feof(f)) + break; + if(error) *error = "invalid WAVE file (004)"; + return false; + } + size = unpack32le_(buffer+4); + /* check if pad byte needed */ + if(size & 1) + size++; + if(!memcmp(buffer, "fmt ", 4)) { + if(fm->format_block) { + if(error) *error = "invalid WAVE file: multiple \"fmt \" chunks (005)"; + return false; + } + fm->format_block = fm->num_blocks; + } + else if(!memcmp(buffer, "data", 4)) { + if(fm->audio_block) { + if(error) *error = "invalid WAVE file: multiple \"data\" chunks (006)"; + return false; + } + fm->audio_block = fm->num_blocks; + } + if(!append_block_(fm, offset, 8 + (memcmp(buffer, "data", 4)? size : 0), error)) + return false; +fprintf(stderr,"@@@@@@ chunk=%c%c%c%c offset=%d size=%d\n",buffer[0],buffer[1],buffer[2],buffer[3],(int)offset,8+(int)size); + if(fseeko(f, size, SEEK_CUR) < 0) { + if(error) *error = "invalid WAVE file: seek error (007)"; + return false; + } + } + if(eof_offset != ftello(f)) { + if(error) *error = "invalid WAVE file: unexpected EOF (008)"; + return false; + } + if(!fm->format_block) { + if(error) *error = "invalid WAVE file: missing \"fmt \" chunk (009)"; + return false; + } + if(!fm->audio_block) { + if(error) *error = "invalid WAVE file: missing \"data\" chunk (010)"; + return false; + } + return true; +} + +static FLAC__bool write_to_flac_(foreign_metadata_t *fm, FILE *fin, FILE *fout, FLAC__Metadata_SimpleIterator *it, const char **error) +{ + static FLAC__byte buffer[4096]; + const unsigned ID_LEN = FLAC__STREAM_METADATA_APPLICATION_ID_LEN/8; + size_t left, block_num = 0; + FLAC__ASSERT(sizeof(buffer) >= ID_LEN); + while(block_num < fm->num_blocks) { + /* find next matching padding block */ + do { + /* even on the first chunk's loop there will be a skippable STREAMINFO block, on subsequent loops we are first moving past the PADDING we just used */ + if(!FLAC__metadata_simple_iterator_next(it)) { + if(error) *error = "no matching PADDING block found (004)"; + return false; + } + } while(FLAC__metadata_simple_iterator_get_block_type(it) != FLAC__METADATA_TYPE_PADDING); + if(FLAC__metadata_simple_iterator_get_block_length(it) != ID_LEN+fm->blocks[block_num].size) { + if(error) *error = "PADDING block with wrong size found (005)"; + return false; + } +fprintf(stderr,"@@@@@@ flac offset = %d\n",(int)FLAC__metadata_simple_iterator_get_block_offset(it)); + /* transfer chunk into APPLICATION block */ + /* first set up the file pointers */ + if(fseek(fin, fm->blocks[block_num].offset, SEEK_SET) < 0) { + if(error) *error = "seek failed in WAVE/AIFF file (006)"; + return false; + } + if(fseek(fout, FLAC__metadata_simple_iterator_get_block_offset(it), SEEK_SET) < 0) { + if(error) *error = "seek failed in FLAC file (007)"; + return false; + } + /* update the type */ + buffer[0] = FLAC__METADATA_TYPE_APPLICATION; + if(FLAC__metadata_simple_iterator_is_last(it)) + buffer[0] |= 0x80; /*MAGIC number*/ + if(fwrite(buffer, 1, 1, fout) < 1) { + if(error) *error = "write failed in FLAC/AIFF file (008)"; + return false; + } + /* length stays the same so skip over it */ + if(fseek(fout, FLAC__STREAM_METADATA_LENGTH_LEN/8, SEEK_CUR) < 0) { + if(error) *error = "seek failed in FLAC file (009)"; + return false; + } + /* write the APPLICATION ID */ + memcpy(buffer, FLAC__FOREIGN_METADATA_APPLICATION_ID, ID_LEN); + if(fwrite(buffer, 1, ID_LEN, fout) < ID_LEN) { + if(error) *error = "write failed in FLAC/AIFF file (010)"; + return false; + } + /* transfer the foreign metadata */ + for(left = fm->blocks[block_num].size; left > 0; ) { + size_t need = min(sizeof(buffer), left); + if(fread(buffer, 1, need, fin) < need) { + if(error) *error = "read failed in WAVE/AIFF file (011)"; + return false; + } + if(fwrite(buffer, 1, need, fout) < need) { + if(error) *error = "write failed in FLAC/AIFF file (012)"; + return false; + } + left -= need; + } + block_num++; + } + return true; +} + +foreign_metadata_t *flac__foreign_metadata_new(void) +{ + return (foreign_metadata_t*)calloc(sizeof(foreign_metadata_t), 1); +} + +void flac__foreign_metadata_delete(foreign_metadata_t *fm) +{ + if(fm) { + if(fm->blocks) + free(fm->blocks); + free(fm); + } +} + +FLAC__bool flac__foreign_metadata_read_from_aiff(foreign_metadata_t *fm, const char *filename, const char **error) +{ + FLAC__bool ok; + FILE *f = fopen(filename, "rb"); + if(!f) { + if(error) *error = "can't open AIFF file for reading (000)"; + return false; + } + ok = read_from_aiff_(fm, f, error); + fclose(f); + return ok; +} + +FLAC__bool flac__foreign_metadata_read_from_wave(foreign_metadata_t *fm, const char *filename, const char **error) +{ + FLAC__bool ok; + FILE *f = fopen(filename, "rb"); + if(!f) { + if(error) *error = "can't open WAVE file for reading (000)"; + return false; + } + ok = read_from_wave_(fm, f, error); + fclose(f); + return ok; +} + +FLAC__bool flac__foreign_metadata_write_to_flac(foreign_metadata_t *fm, const char *infilename, const char *outfilename, const char **error) +{ + FLAC__bool ok; + FILE *fin, *fout; + FLAC__Metadata_SimpleIterator *it = FLAC__metadata_simple_iterator_new(); + if(!it) { + if(error) *error = "out of memory (000)"; + return false; + } + if(!FLAC__metadata_simple_iterator_init(it, outfilename, /*read_only=*/true, /*preserve_file_stats=*/false)) { + if(error) *error = "can't initialize iterator (001)"; + FLAC__metadata_simple_iterator_delete(it); + return false; + } + if(0 == (fin = fopen(infilename, "rb"))) { + if(error) *error = "can't open WAVE/AIFF file for reading (002)"; + FLAC__metadata_simple_iterator_delete(it); + return false; + } + if(0 == (fout = fopen(outfilename, "r+b"))) { + if(error) *error = "can't open FLAC file for updating (003)"; + FLAC__metadata_simple_iterator_delete(it); + fclose(fin); + return false; + } + ok = write_to_flac_(fm, fin, fout, it, error); + FLAC__metadata_simple_iterator_delete(it); + fclose(fin); + fclose(fout); + return ok; +} + +FLAC__bool flac__foreign_metadata_read_from_flac(foreign_metadata_t *fm, const char *filename, const char **error) +{ +} + +FLAC__bool flac__foreign_metadata_write_to_aiff(foreign_metadata_t *fm, const char *infilename, const char *outfilename, const char **error) +{ +} + +FLAC__bool flac__foreign_metadata_write_to_wave(foreign_metadata_t *fm, const char *infilename, const char *outfilename, const char **error) +{ +} diff --git a/src/flac/foreign_metadata.h b/src/flac/foreign_metadata.h new file mode 100644 index 00000000..7e7c4aa0 --- /dev/null +++ b/src/flac/foreign_metadata.h @@ -0,0 +1,55 @@ +/* flac - Command-line FLAC encoder/decoder + * Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef flac__foreign_metadata_h +#define flac__foreign_metadata_h + +#if HAVE_CONFIG_H +# include +#endif + +#include "FLAC/metadata.h" +#include "utils.h" + +typedef struct { + /* for encoding, this will be the offset in the WAVE/AIFF file of the chunk */ + /* for decoding, this will be the offset in the FLAC file of the chunk data inside the APPLICATION block */ + off_t offset; + FLAC__uint32 size; +} foreign_block_t; + +typedef struct { + foreign_block_t *blocks; + size_t num_blocks; + size_t format_block; /* block number of 'fmt ' or 'COMM' chunk */ + size_t audio_block; /* block number of 'data' or 'SSND' chunk */ +} foreign_metadata_t; + +foreign_metadata_t *flac__foreign_metadata_new(void); + +void flac__foreign_metadata_delete(foreign_metadata_t *fm); + +FLAC__bool flac__foreign_metadata_read_from_aiff(foreign_metadata_t *fm, const char *filename, const char **error); +FLAC__bool flac__foreign_metadata_read_from_wave(foreign_metadata_t *fm, const char *filename, const char **error); +FLAC__bool flac__foreign_metadata_write_to_flac(foreign_metadata_t *fm, const char *infilename, const char *outfilename, const char **error); + +FLAC__bool flac__foreign_metadata_read_from_flac(foreign_metadata_t *fm, const char *filename, const char **error); +FLAC__bool flac__foreign_metadata_write_to_aiff(foreign_metadata_t *fm, const char *infilename, const char *outfilename, const char **error); +FLAC__bool flac__foreign_metadata_write_to_wave(foreign_metadata_t *fm, const char *infilename, const char *outfilename, const char **error); + +#endif diff --git a/src/flac/main.c b/src/flac/main.c index 06a61c21..e0075749 100644 --- a/src/flac/main.c +++ b/src/flac/main.c @@ -100,23 +100,24 @@ static struct share__option long_options_[] = { /* * general options */ - { "help" , share__no_argument, 0, 'h' }, - { "explain" , share__no_argument, 0, 'H' }, - { "version" , share__no_argument, 0, 'v' }, - { "decode" , share__no_argument, 0, 'd' }, - { "analyze" , share__no_argument, 0, 'a' }, - { "test" , share__no_argument, 0, 't' }, - { "stdout" , share__no_argument, 0, 'c' }, - { "silent" , share__no_argument, 0, 's' }, - { "totally-silent" , share__no_argument, 0, 0 }, - { "warnings-as-errors", share__no_argument, 0, 'w' }, - { "force" , share__no_argument, 0, 'f' }, - { "delete-input-file" , share__no_argument, 0, 0 }, - { "output-prefix" , share__required_argument, 0, 0 }, - { "output-name" , share__required_argument, 0, 'o' }, - { "skip" , share__required_argument, 0, 0 }, - { "until" , share__required_argument, 0, 0 }, - { "channel-map" , share__required_argument, 0, 0 }, /* undocumented */ + { "help" , share__no_argument, 0, 'h' }, + { "explain" , share__no_argument, 0, 'H' }, + { "version" , share__no_argument, 0, 'v' }, + { "decode" , share__no_argument, 0, 'd' }, + { "analyze" , share__no_argument, 0, 'a' }, + { "test" , share__no_argument, 0, 't' }, + { "stdout" , share__no_argument, 0, 'c' }, + { "silent" , share__no_argument, 0, 's' }, + { "totally-silent" , share__no_argument, 0, 0 }, + { "warnings-as-errors" , share__no_argument, 0, 'w' }, + { "force" , share__no_argument, 0, 'f' }, + { "delete-input-file" , share__no_argument, 0, 0 }, + { "keep-foreign-metadata" , share__no_argument, 0, 0 }, + { "output-prefix" , share__required_argument, 0, 0 }, + { "output-name" , share__required_argument, 0, 'o' }, + { "skip" , share__required_argument, 0, 0 }, + { "until" , share__required_argument, 0, 0 }, + { "channel-map" , share__required_argument, 0, 0 }, /* undocumented */ /* * decoding options @@ -188,6 +189,7 @@ static struct share__option long_options_[] = { { "no-force" , share__no_argument, 0, 0 }, { "no-seektable" , share__no_argument, 0, 0 }, { "no-delete-input-file" , share__no_argument, 0, 0 }, + { "no-keep-foreign-metadata" , share__no_argument, 0, 0 }, { "no-replay-gain" , share__no_argument, 0, 0 }, { "no-ignore-chunk-sizes" , share__no_argument, 0, 0 }, { "no-sector-align" , share__no_argument, 0, 0 }, @@ -241,6 +243,7 @@ static struct { FLAC__bool force_aiff_format; FLAC__bool force_raw_format; FLAC__bool delete_input; + FLAC__bool keep_foreign_metadata; FLAC__bool replay_gain; FLAC__bool ignore_chunk_sizes; FLAC__bool sector_align; @@ -434,8 +437,8 @@ int do_it(void) * whole file. */ if( - (option_values.padding >= 0 && option_values.padding < GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED) || - (option_values.padding < 0 && FLAC_ENCODE__DEFAULT_PADDING < GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED) + (option_values.padding >= 0 && option_values.padding < (int)GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED) || + (option_values.padding < 0 && FLAC_ENCODE__DEFAULT_PADDING < (int)GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED) ) { flac__utils_printf(stderr, 1, "NOTE: --replay-gain may leave a small PADDING block even with --no-padding\n"); option_values.padding = GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED; @@ -453,6 +456,15 @@ int do_it(void) if(!option_values.mode_decode && 0 != option_values.cuesheet_filename && option_values.num_files > 1) { return usage_error("ERROR: --cuesheet cannot be used when encoding multiple files\n"); } + if(option_values.keep_foreign_metadata) { + /* we're not going to try and support the re-creation of broken WAVE files */ + if(option_values.ignore_chunk_sizes) + return usage_error("ERROR: using --keep-foreign-metadata cannot be used with --ignore-chunk-sizes\n"); + /*@@@@@@*/ + if(option_values.delete_input) + return usage_error("ERROR: using --delete-input-file with --keep-foreign-metadata has been disabled until more testing has been done.\n"); + flac__utils_printf(stderr, 1, "NOTE: --keep-foreign-metadata is a new feature; make sure to test the output file before deleting the original.\n"); + } } flac__utils_printf(stderr, 2, "\n"); @@ -548,6 +560,7 @@ FLAC__bool init_options(void) option_values.force_aiff_format = false; option_values.force_raw_format = false; option_values.delete_input = false; + option_values.keep_foreign_metadata = false; option_values.replay_gain = false; option_values.ignore_chunk_sizes = false; option_values.sector_align = false; @@ -645,6 +658,9 @@ int parse_option(int short_option, const char *long_option, const char *option_a else if(0 == strcmp(long_option, "delete-input-file")) { option_values.delete_input = true; } + else if(0 == strcmp(long_option, "keep-foreign-metadata")) { + option_values.keep_foreign_metadata = true; + } else if(0 == strcmp(long_option, "output-prefix")) { FLAC__ASSERT(0 != option_argument); option_values.output_prefix = option_argument; @@ -814,6 +830,9 @@ int parse_option(int short_option, const char *long_option, const char *option_a else if(0 == strcmp(long_option, "no-delete-input-file")) { option_values.delete_input = false; } + else if(0 == strcmp(long_option, "no-keep-foreign-metadata")) { + option_values.keep_foreign_metadata = false; + } else if(0 == strcmp(long_option, "no-replay-gain")) { option_values.replay_gain = false; } @@ -1179,6 +1198,7 @@ void show_help(void) printf(" -o, --output-name=FILENAME Force the output file name\n"); printf(" --output-prefix=STRING Prepend STRING to output names\n"); printf(" --delete-input-file Deletes after a successful encode/decode\n"); + printf(" --keep-foreign-metadata Save/restore WAVE or AIFF non-audio chunks\n"); printf(" --skip={#|mm:ss.ss} Skip the given initial samples for each input\n"); printf(" --until={#|[+|-]mm:ss.ss} Stop at the given sample for each input file\n"); #if FLAC__HAS_OGG @@ -1236,6 +1256,7 @@ void show_help(void) printf(" --no-adaptive-mid-side\n"); printf(" --no-decode-through-errors\n"); printf(" --no-delete-input-file\n"); + printf(" --no-keep-foreign-metadata\n"); printf(" --no-exhaustive-model-search\n"); printf(" --no-lax\n"); printf(" --no-mid-side\n"); @@ -1263,7 +1284,7 @@ void show_explain(void) usage_header(); usage_summary(); printf("For encoding:\n"); - printf(" The input file(s) may be a PCM RIFF WAVE file, AIFF (or uncompressed AIFF-C)\n"); + printf(" The input file(s) may be a PCM WAVE file, AIFF (or uncompressed AIFF-C)\n"); printf(" file, or raw samples.\n"); printf(" The output file(s) will be in native FLAC or Ogg FLAC format\n"); printf("For decoding, the reverse is true.\n"); @@ -1313,6 +1334,13 @@ void show_explain(void) printf(" successful encode or decode. If there was an\n"); printf(" error (including a verify error) the input file\n"); printf(" is left intact.\n"); + printf(" --keep-foreign-metadata If encoding, save WAVE or AIFF non-audio chunks\n"); + printf(" in FLAC metadata. If decoding, restore any saved\n"); + printf(" non-audio chunks from FLAC metadata when writing\n"); + printf(" the decoded file. Foreign metadata cannot be\n"); + printf(" transcoded, e.g. WAVE chunks saved in a FLAC file\n"); + printf(" cannot be restored when decoding to AIFF. Input\n"); + printf(" and output must be regular files.\n"); printf(" --skip={#|mm:ss.ss} Skip the first # samples of each input file; can\n"); printf(" be used both for encoding and decoding. The\n"); printf(" alternative form mm:ss.ss can be used to specify\n"); @@ -1556,6 +1584,7 @@ void show_explain(void) printf(" --no-adaptive-mid-side\n"); printf(" --no-decode-through-errors\n"); printf(" --no-delete-input-file\n"); + printf(" --no-keep-foreign-metadata\n"); printf(" --no-exhaustive-model-search\n"); printf(" --no-lax\n"); printf(" --no-mid-side\n"); @@ -1589,7 +1618,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ FILE *encode_infile; FLAC__byte lookahead[12]; unsigned lookahead_length = 0; - FileFormat fmt = RAW; + FileFormat input_format = RAW; FLAC__bool is_aifc = false; int retval; off_t infilesize; @@ -1618,28 +1647,28 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ if(!option_values.force_raw_format) { /* first set format based on name */ if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".wav")) - fmt= WAV; + input_format = WAV; else if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".aif")) - fmt= AIF; + input_format = AIF; else if(strlen(infilename) >= 5 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-5), ".aiff")) - fmt= AIF; + input_format = AIF; else if(strlen(infilename) >= 5 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-5), ".flac")) - fmt= FLAC; + input_format = FLAC; else if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".oga")) - fmt= OGGFLAC; + input_format = OGGFLAC; else if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".ogg")) - fmt= OGGFLAC; + input_format = OGGFLAC; /* attempt to guess the file type based on the first 12 bytes */ if((lookahead_length = fread(lookahead, 1, 12, encode_infile)) < 12) { - if(fmt != RAW) { - format_mistake(infilename, fmt, RAW); + if(input_format != RAW) { + format_mistake(infilename, input_format, RAW); if(option_values.treat_warnings_as_errors) { conditional_fclose(encode_infile); return 1; } } - fmt= RAW; + input_format = RAW; } else { if(!strncmp((const char *)lookahead, "ID3", 3)) { @@ -1647,37 +1676,48 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ return 1; } else if(!strncmp((const char *)lookahead, "RIFF", 4) && !strncmp((const char *)lookahead+8, "WAVE", 4)) - fmt= WAV; + input_format = WAV; else if(!strncmp((const char *)lookahead, "FORM", 4) && !strncmp((const char *)lookahead+8, "AIFF", 4)) - fmt= AIF; + input_format = AIF; else if(!strncmp((const char *)lookahead, "FORM", 4) && !strncmp((const char *)lookahead+8, "AIFC", 4)) { - fmt= AIF; + input_format = AIF; is_aifc = true; } else if(!memcmp(lookahead, FLAC__STREAM_SYNC_STRING, sizeof(FLAC__STREAM_SYNC_STRING))) - fmt= FLAC; + input_format = FLAC; /* this could be made more accurate by looking at the first packet */ else if(!memcmp(lookahead, "OggS", 4)) - fmt= OGGFLAC; + input_format = OGGFLAC; else { - if(fmt != RAW) { - format_mistake(infilename, fmt, RAW); + if(input_format != RAW) { + format_mistake(infilename, input_format, RAW); if(option_values.treat_warnings_as_errors) { conditional_fclose(encode_infile); return 1; } } - fmt= RAW; + input_format = RAW; } } } + if(option_values.keep_foreign_metadata) { + if(encode_infile == stdin || option_values.force_to_stdout) { + conditional_fclose(encode_infile); + return usage_error("ERROR: --keep-foreign-metadata cannot be used when encoding from stdin or to stdout\n"); + } + if(input_format != WAV && input_format != AIF) { + conditional_fclose(encode_infile); + return usage_error("ERROR: --keep-foreign-metadata can only be used with WAVE or AIFF input\n"); + } + } + /* * Error if output file already exists (and -f not used). * Use grabbag__file_get_filesize() as a cheap way to check. */ if(!option_values.test_only && !option_values.force_file_overwrite && strcmp(outfilename, "-") && grabbag__file_get_filesize(outfilename) != (off_t)(-1)) { - if(fmt == FLAC) { + if(input_format == FLAC) { /* need more detailed error message when re-flac'ing to avoid confusing the user */ flac__utils_printf(stderr, 1, "ERROR: output file %s already exists.\n\n" @@ -1688,7 +1728,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ outfilename ); } - else if(fmt == OGGFLAC) { + else if(input_format == OGGFLAC) { /* need more detailed error message when re-flac'ing to avoid confusing the user */ flac__utils_printf(stderr, 1, "ERROR: output file %s already exists.\n\n" @@ -1706,7 +1746,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ } if(option_values.format_input_size >= 0) { - if (fmt != RAW || infilesize >= 0) { + if (input_format != RAW || infilesize >= 0) { flac__utils_printf(stderr, 1, "ERROR: can only use --input-size when encoding raw samples from stdin\n"); conditional_fclose(encode_infile); return 1; @@ -1716,25 +1756,25 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ } } - if(option_values.sector_align && (fmt == FLAC || fmt == OGGFLAC)) { + if(option_values.sector_align && (input_format == FLAC || input_format == OGGFLAC)) { flac__utils_printf(stderr, 1, "ERROR: can't use --sector-align when the input file is FLAC or Ogg FLAC\n"); conditional_fclose(encode_infile); return 1; } - if(option_values.sector_align && fmt == RAW && infilesize < 0) { + if(option_values.sector_align && input_format == RAW && infilesize < 0) { flac__utils_printf(stderr, 1, "ERROR: can't use --sector-align when the input size is unknown\n"); conditional_fclose(encode_infile); return 1; } - if(fmt == RAW) { + if(input_format == RAW) { if(option_values.format_is_big_endian < 0 || option_values.format_is_unsigned_samples < 0 || option_values.format_channels < 0 || option_values.format_bps < 0 || option_values.format_sample_rate < 0) { conditional_fclose(encode_infile); return usage_error("ERROR: for encoding a raw file you must specify a value for --endian, --sign, --channels, --bps, and --sample-rate\n"); } } - if(encode_infile == stdin || option_values.force_to_stdout) { + if(/*@@@@@@why no stdin?*/encode_infile == stdin || option_values.force_to_stdout) { if(option_values.replay_gain) { conditional_fclose(encode_infile); return usage_error("ERROR: --replay-gain cannot be used when encoding to stdout\n"); @@ -1809,7 +1849,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ strcat(internal_outfilename, tmp_suffix); } - if(fmt == RAW) { + if(input_format == RAW) { raw_encode_options_t options; options.common = common_options; @@ -1821,22 +1861,34 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ retval = flac__encode_raw(encode_infile, infilesize, infilename, internal_outfilename? internal_outfilename : outfilename, lookahead, lookahead_length, options); } - else if(fmt == FLAC || fmt == OGGFLAC) { + else if(input_format == FLAC || input_format == OGGFLAC) { flac_encode_options_t options; options.common = common_options; - retval = flac__encode_flac(encode_infile, infilesize, infilename, internal_outfilename? internal_outfilename : outfilename, lookahead, lookahead_length, options, fmt==OGGFLAC); + retval = flac__encode_flac(encode_infile, infilesize, infilename, internal_outfilename? internal_outfilename : outfilename, lookahead, lookahead_length, options, input_format==OGGFLAC); } else { wav_encode_options_t options; options.common = common_options; - if(fmt == AIF) + /* read foreign metadata if requested */ + if(option_values.keep_foreign_metadata) { + if(0 == (options.foreign_metadata = flac__foreign_metadata_new())) { + flac__utils_printf(stderr, 1, "ERROR: creating foreign metadata object\n"); + conditional_fclose(encode_infile); + return 1; + } + } + + if(input_format == AIF) retval = flac__encode_aif(encode_infile, infilesize, infilename, internal_outfilename? internal_outfilename : outfilename, lookahead, lookahead_length, options, is_aifc); else retval = flac__encode_wav(encode_infile, infilesize, infilename, internal_outfilename? internal_outfilename : outfilename, lookahead, lookahead_length, options); + + if(option_values.keep_foreign_metadata) + flac__foreign_metadata_delete(options.foreign_metadata); } if(retval == 0) { @@ -1850,6 +1902,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ 0 != (error = grabbag__replaygain_store_to_file_title(internal_outfilename? internal_outfilename : outfilename, title_gain, title_peak, /*preserve_modtime=*/true)) ) { flac__utils_printf(stderr, 1, "%s: ERROR writing ReplayGain reference/title tags (%s)\n", outfilename, error); + retval = 1; } } if(strcmp(infilename, "-")) @@ -1891,6 +1944,7 @@ int decode_file(const char *infilename) { int retval; FLAC__bool treat_as_ogg = false; + FileFormat output_format = WAV; decode_options_t common_options; const char *outfilename = get_decoded_outfilename(infilename); @@ -1908,11 +1962,29 @@ int decode_file(const char *infilename) return 1; } + if(option_values.force_raw_format) + output_format = RAW; + else if( + option_values.force_aiff_format || + (strlen(outfilename) >= 4 && 0 == FLAC__STRCASECMP(outfilename+(strlen(outfilename)-4), ".aif")) || + (strlen(outfilename) >= 5 && 0 == FLAC__STRCASECMP(outfilename+(strlen(outfilename)-5), ".aiff")) + ) + output_format = AIF; + else + output_format = WAV; + if(!option_values.test_only && !option_values.analyze) { - if(option_values.force_raw_format && (option_values.format_is_big_endian < 0 || option_values.format_is_unsigned_samples < 0)) + if(output_format == RAW && (option_values.format_is_big_endian < 0 || option_values.format_is_unsigned_samples < 0)) return usage_error("ERROR: for decoding to a raw file you must specify a value for --endian and --sign\n"); } + if(option_values.keep_foreign_metadata) { + if(0 == strcmp(infilename, "-") || 0 == strcmp(outfilename, "-")) + return usage_error("ERROR: --keep-foreign-metadata cannot be used when decoding from stdin or to stdout\n"); + if(output_format != WAV && output_format != AIF) + return usage_error("ERROR: --keep-foreign-metadata can only be used with WAVE or AIFF output\n"); + } + if(option_values.use_ogg) treat_as_ogg = true; else if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".oga")) @@ -1956,21 +2028,7 @@ int decode_file(const char *infilename) #endif common_options.channel_map_none = option_values.channel_map_none; - if(!option_values.force_raw_format) { - wav_decode_options_t options; - - options.common = common_options; - - if( - option_values.force_aiff_format || - (strlen(outfilename) >= 4 && 0 == FLAC__STRCASECMP(outfilename+(strlen(outfilename)-4), ".aif")) || - (strlen(outfilename) >= 5 && 0 == FLAC__STRCASECMP(outfilename+(strlen(outfilename)-5), ".aiff")) - ) - retval = flac__decode_aiff(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); - else - retval = flac__decode_wav(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); - } - else { + if(output_format == RAW) { raw_decode_options_t options; options.common = common_options; @@ -1979,6 +2037,27 @@ int decode_file(const char *infilename) retval = flac__decode_raw(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); } + else { + wav_decode_options_t options; + + options.common = common_options; + + /* read foreign metadata if requested */ + if(option_values.keep_foreign_metadata) { + if(0 == (options.foreign_metadata = flac__foreign_metadata_new())) { + flac__utils_printf(stderr, 1, "ERROR: creating foreign metadata object\n"); + return 1; + } + } + + if(output_format == AIF) + retval = flac__decode_aiff(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); + else + retval = flac__decode_wav(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); + + if(option_values.keep_foreign_metadata) + flac__foreign_metadata_delete(options.foreign_metadata); + } if(retval == 0 && strcmp(infilename, "-")) { if(strcmp(outfilename, "-"))