initial support for new --keep-foreign-metadata options, saving done, restoring TODO
authorJosh Coalson <jcoalson@users.sourceforce.net>
Tue, 14 Aug 2007 00:36:58 +0000 (00:36 +0000)
committerJosh Coalson <jcoalson@users.sourceforce.net>
Tue, 14 Aug 2007 00:36:58 +0000 (00:36 +0000)
src/flac/Makefile.am
src/flac/Makefile.lite
src/flac/decode.c
src/flac/decode.h
src/flac/encode.c
src/flac/encode.h
src/flac/foreign_metadata.c [new file with mode: 0644]
src/flac/foreign_metadata.h [new file with mode: 0644]
src/flac/main.c

index 3ebd8e3..cea8a52 100644 (file)
@@ -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
index 7d1795f..9e8a0cc 100644 (file)
@@ -36,6 +36,7 @@ SRCS_C = \
        analyze.c \
        decode.c \
        encode.c \
+       foreign_metadata.c \
        local_string_utils.c \
        main.c \
        utils.c \
index 30a4b17..1847c59 100644 (file)
@@ -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
index 2d3c3ae..2ab9deb 100644 (file)
 #ifndef flac__decode_h
 #define flac__decode_h
 
+#if HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
 #include "analyze.h"
+#include "foreign_metadata.h"
 #include "utils.h"
 #include "share/replaygain_synthesis.h"
 
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#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 {
index 5da5139..973af63 100644 (file)
@@ -38,7 +38,7 @@
 #include <math.h> /* for floor() */
 #include <stdio.h> /* for FILE etc. */
 #include <stdlib.h> /* for malloc */
-#include <string.h> /* for strcmp(), strerror( */
+#include <string.h> /* 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;
 }
index 1394c89..13ee7d3 100644 (file)
 #ifndef flac__encode_h
 #define flac__encode_h
 
-/* needed because of off_t */
 #if HAVE_CONFIG_H
 #  include <config.h>
 #endif
 
 #include "FLAC/metadata.h"
+#include "foreign_metadata.h"
 #include "utils.h"
 
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#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 (file)
index 0000000..8883f96
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#if defined _MSC_VER || defined __MINGW32__
+#include <sys/types.h> /* for off_t */
+#if _MSC_VER <= 1600 /* @@@ [2G limit] */
+#define fseeko fseek
+#define ftello ftell
+#endif
+#endif
+#include <stdio.h> /* for FILE etc. */
+#include <stdlib.h> /* for calloc() etc. */
+#include <string.h> /* 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 (file)
index 0000000..7e7c4aa
--- /dev/null
@@ -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 <config.h>
+#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
index 06a61c2..e007574 100644 (file)
@@ -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,28 +2028,35 @@ 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;
+       if(output_format == RAW) {
+               raw_decode_options_t options;
 
                options.common = common_options;
+               options.is_big_endian = option_values.format_is_big_endian;
+               options.is_unsigned_samples = option_values.format_is_unsigned_samples;
 
-               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);
+               retval = flac__decode_raw(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options);
        }
        else {
-               raw_decode_options_t options;
+               wav_decode_options_t options;
 
                options.common = common_options;
-               options.is_big_endian = option_values.format_is_big_endian;
-               options.is_unsigned_samples = option_values.format_is_unsigned_samples;
 
-               retval = flac__decode_raw(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, 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, "-")) {