diff --git a/src/flac/Makefile.lite b/src/flac/Makefile.lite index 341ee1cd..78d62a24 100644 --- a/src/flac/Makefile.lite +++ b/src/flac/Makefile.lite @@ -24,16 +24,16 @@ topdir = ../.. PROGRAM_NAME = flac ifeq ($(DARWIN_BUILD),yes) INCLUDES = -I./include -I$(topdir)/include -LIBS = -lFLAC -lgain_analysis -lgetopt -lutf8 -lc -lm +LIBS = -lFLAC -lreplaygain -lgain_analysis -lgetopt -lutf8 -lc -lm else #@@@ TODO: conditionalize ogg includes, defines, and -logg ifeq ($(SOLARIS_BUILD),yes) INCLUDES = -I./include -I$(topdir)/include -I$(HOME)/local/include -DFLAC__HAS_OGG -LIBS = -lOggFLAC -lFLAC -lgain_analysis -lgetopt -lutf8 -lm -L$(HOME)/local/lib -logg +LIBS = -lOggFLAC -lFLAC -lreplaygain -lgain_analysis -lgetopt -lutf8 -lm -L$(HOME)/local/lib -logg else #@@@ TODO: conditionalize ogg includes, defines, and -logg INCLUDES = -I./include -I$(topdir)/include -I$(HOME)/local/include -DFLAC__HAS_OGG -LIBS = -lOggFLAC -lFLAC -lgain_analysis -lgetopt -lutf8 -lm -L$(HOME)/local/lib -logg +LIBS = -lOggFLAC -lFLAC -lreplaygain -lgain_analysis -lgetopt -lutf8 -lm -L$(HOME)/local/lib -logg endif endif diff --git a/src/flac/Makefile.vc b/src/flac/Makefile.vc index 2deadcfb..853ad6fe 100644 --- a/src/flac/Makefile.vc +++ b/src/flac/Makefile.vc @@ -40,7 +40,7 @@ OBJS= $(C_FILES:.c=.obj) all: flac.exe flac.exe: $(OBJS) - link.exe /libpath:"..\..\obj\lib" -out:../../obj/bin/$*.exe $(OBJS) libOggFLAC.lib libFLAC.lib ogg_static.lib gain_analysis.lib getopt.lib utf8.lib + link.exe /libpath:"..\..\obj\lib" -out:../../obj/bin/$*.exe $(OBJS) libOggFLAC.lib libFLAC.lib ogg_static.lib replaygain.lib gain_analysis.lib getopt.lib utf8.lib clean: -del *.obj *.pch diff --git a/src/flac/flac.dsp b/src/flac/flac.dsp index ab5a0823..6355a198 100644 --- a/src/flac/flac.dsp +++ b/src/flac/flac.dsp @@ -51,7 +51,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 -# ADD LINK32 ..\..\obj\lib\libOggFLAC.lib ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\ogg_static.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 ..\..\obj\lib\libOggFLAC.lib ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\ogg_static.lib ..\..\obj\lib\replaygain.lib ..\..\obj\lib\gain_analysis.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /machine:I386 !ELSEIF "$(CFG)" == "flac - Win32 Debug" @@ -76,7 +76,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept -# ADD LINK32 ..\..\obj\lib\libOggFLAC.lib ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\ogg_static.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ..\..\obj\lib\libOggFLAC.lib ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\ogg_static.lib ..\..\obj\lib\replaygain.lib ..\..\obj\lib\gain_analysis.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept !ENDIF diff --git a/src/metaflac/Makefile.lite b/src/metaflac/Makefile.lite index 0d897ca3..7cddcce8 100644 --- a/src/metaflac/Makefile.lite +++ b/src/metaflac/Makefile.lite @@ -23,7 +23,7 @@ topdir = ../.. PROGRAM_NAME = metaflac INCLUDES = -I./include -I$(topdir)/include -LIBS = -lFLAC -lgain_analysis -lgetopt -lutf8 -lm +LIBS = -lFLAC -lreplaygain -lgain_analysis -lgetopt -lutf8 -lm OBJS = \ main.o diff --git a/src/metaflac/Makefile.vc b/src/metaflac/Makefile.vc index 252d5667..4b786034 100644 --- a/src/metaflac/Makefile.vc +++ b/src/metaflac/Makefile.vc @@ -33,7 +33,7 @@ OBJS= $(C_FILES:.c=.obj) all: metaflac.exe metaflac.exe: $(OBJS) - link.exe /libpath:"..\..\obj\lib" -out:../../obj/bin/$*.exe $(OBJS) libFLAC.lib gain_analysis.lib getopt.lib utf8.lib + link.exe /libpath:"..\..\obj\lib" -out:../../obj/bin/$*.exe $(OBJS) libFLAC.lib replaygain.lib gain_analysis.lib getopt.lib utf8.lib clean: -del *.obj *.pch diff --git a/src/metaflac/main.c b/src/metaflac/main.c index 20e4c0cf..fbfe63e9 100644 --- a/src/metaflac/main.c +++ b/src/metaflac/main.c @@ -27,6 +27,7 @@ more powerful operations yet to add: #include "FLAC/assert.h" #include "FLAC/metadata.h" +#include "share/replaygain.h" #include "share/utf8.h" #include #include @@ -81,6 +82,7 @@ static struct FLAC__share__option long_options_[] = { { "set-vc-field", 1, 0, 0 }, { "import-vc-from", 1, 0, 0 }, { "export-vc-to", 1, 0, 0 }, + { "add-replay-gain", 0, 0, 0 }, { "add-padding", 1, 0, 0 }, /* major operations */ { "help", 0, 0, 0 }, @@ -128,6 +130,7 @@ typedef enum { OP__SET_VC_FIELD, OP__IMPORT_VC_FROM, OP__EXPORT_VC_TO, + OP__ADD_REPLAY_GAIN, OP__ADD_PADDING, OP__LIST, OP__APPEND, @@ -287,6 +290,9 @@ static FLAC__bool do_major_operation__remove_all(FLAC__Metadata_Chain *chain, co static FLAC__bool do_shorthand_operations(const CommandLineOptions *options); static FLAC__bool do_shorthand_operations_on_file(const char *filename, const CommandLineOptions *options); static FLAC__bool do_shorthand_operation(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool utf8_convert); +static FLAC__bool do_shorthand_operation__add_replay_gain(char **filenames, unsigned num_files); +static FLAC__bool do_replaygain_analyze_file(const char *filename, float *title_gain, float *title_peak); +static FLAC__bool do_replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak); static FLAC__bool do_shorthand_operation__add_padding(const char *filename, FLAC__Metadata_Chain *chain, unsigned length, FLAC__bool *needs_write); static FLAC__bool do_shorthand_operation__streaminfo(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write); static FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool raw); @@ -613,6 +619,9 @@ FLAC__bool parse_option(int option_index, const char *option_argument, CommandLi ok = false; } } + else if(0 == strcmp(opt, "add-replay-gain")) { + (void) append_shorthand_operation(options, OP__ADD_REPLAY_GAIN); + } else if(0 == strcmp(opt, "add-padding")) { op = append_shorthand_operation(options, OP__ADD_PADDING); FLAC__ASSERT(0 != option_argument); @@ -951,6 +960,18 @@ int long_usage(const char *message, ...) fprintf(out, "--export-vc-to=file Export Vorbis comments to a file. Use '-' for stdin.\n"); fprintf(out, " Each line will be of the form NAME=VALUE. Specify\n"); fprintf(out, " --no-utf8-convert if necessary.\n"); + fprintf(out, "--add-replay-gain Calculates the title and album gains/peaks of the given\n"); + fprintf(out, " FLAC files as if all the files were part of one album,\n"); + fprintf(out, " then stores them in the VORBIS_COMMENT block. The tags\n"); + fprintf(out, " are the same as those used by vorbisgain. Existing\n"); + fprintf(out, " ReplayGain tags will be replaced. If only one FLAC file\n"); + fprintf(out, " is given, the album and title gains will be the same.\n"); + fprintf(out, " Since this operation requires two passes, it is always\n"); + fprintf(out, " executed last, after all other operations have been\n"); + fprintf(out, " completed and written to disk. All FLAC files specified\n"); + fprintf(out, " must have the same resolution, sample rate, and number\n"); + fprintf(out, " of channels. The sample rate must be one of 8, 11.025,\n"); + fprintf(out, " 12, 16, 22.05, 24, 32, 44.1, or 48 kHz.\n"); fprintf(out, "--add-padding=length Add a padding block of the given length (in bytes).\n"); fprintf(out, " The overall length of the new block will be 4 + length;\n"); fprintf(out, " the extra 4 bytes is for the metadata block header.\n"); @@ -1518,6 +1539,14 @@ FLAC__bool do_shorthand_operations(const CommandLineOptions *options) for(i = 0; i < options->num_files; i++) ok &= do_shorthand_operations_on_file(options->filenames[i], options); + /* check if OP__ADD_REPLAY_GAIN requested */ + if(ok && options->num_files > 0) { + for(i = 0; i < options->ops.num_operations; i++) { + if(options->ops.operations[i].type == OP__ADD_REPLAY_GAIN) + ok = do_shorthand_operation__add_replay_gain(options->filenames, options->num_files); + } + } + return ok; } @@ -1597,6 +1626,10 @@ FLAC__bool do_shorthand_operation(const char *filename, FLAC__Metadata_Chain *ch case OP__EXPORT_VC_TO: ok = do_shorthand_operation__vorbis_comment(filename, chain, operation, needs_write, !utf8_convert); break; + case OP__ADD_REPLAY_GAIN: + /* this command is always executed last */ + ok = true; + break; case OP__ADD_PADDING: ok = do_shorthand_operation__add_padding(filename, chain, operation->argument.add_padding.length, needs_write); break; @@ -1609,6 +1642,115 @@ FLAC__bool do_shorthand_operation(const char *filename, FLAC__Metadata_Chain *ch return ok; } +FLAC__bool do_shorthand_operation__add_replay_gain(char **filenames, unsigned num_files) +{ + static const unsigned valid_sample_rates[] = { + 8000, + 11025, + 12000, + 16000, + 22050, + 24000, + 32000, + 44100, + 48000 + }; + static const unsigned n_valid_sample_rates = sizeof(valid_sample_rates) / sizeof(valid_sample_rates[0]); + + FLAC__StreamMetadata streaminfo; + float *title_gains = 0, *title_peaks = 0; + float album_gain = 0.0, album_peak = 0.0; + unsigned sample_rate = 0; + unsigned bits_per_sample = 0; + unsigned channels = 0; + unsigned i, j; + FLAC__bool first = true; + + FLAC__ASSERT(num_files > 0); + + for(i = 0; i < num_files; i++) { + FLAC__ASSERT(0 != filenames[i]); + if(!FLAC__metadata_get_streaminfo(filenames[i], &streaminfo)) { + fprintf(stderr, "%s: ERROR: can't open file or get STREAMINFO block\n", filenames[i]); + return false; + } + if(first) { + first = false; + sample_rate = streaminfo.data.stream_info.sample_rate; + bits_per_sample = streaminfo.data.stream_info.bits_per_sample; + channels = streaminfo.data.stream_info.channels; + } + else { + if(sample_rate != streaminfo.data.stream_info.sample_rate) { + fprintf(stderr, "%s: ERROR: sample rate of %u Hz does not match previous files' %u Hz\n", filenames[i], streaminfo.data.stream_info.sample_rate, sample_rate); + return false; + } + if(bits_per_sample != streaminfo.data.stream_info.bits_per_sample) { + fprintf(stderr, "%s: ERROR: resolution of %u bps does not match previous files' %u bps\n", filenames[i], streaminfo.data.stream_info.bits_per_sample, bits_per_sample); + return false; + } + if(channels != streaminfo.data.stream_info.channels) { + fprintf(stderr, "%s: ERROR: # channels (%u) does not match previous files' (%u)\n", filenames[i], streaminfo.data.stream_info.channels, channels); + return false; + } + } + for(j = 0; j < n_valid_sample_rates; j++) + if(sample_rate == valid_sample_rates[j]) + break; + if(j == n_valid_sample_rates) { + fprintf(stderr, "%s: ERROR: sample rate of %u Hz is not supported\n", filenames[i], sample_rate); + return false; + } + if(channels != 1 && channels != 2) { + fprintf(stderr, "%s: ERROR: # of channels (%u) is not supported, must be 1 or 2\n", filenames[i], channels); + return false; + } + } + FLAC__ASSERT(bits_per_sample >= FLAC__MIN_BITS_PER_SAMPLE && bits_per_sample <= FLAC__MAX_BITS_PER_SAMPLE); + + if(!FLAC__replaygain_init(sample_rate)) + FLAC__ASSERT(0); + + if( + 0 == (title_gains = (float*)malloc(sizeof(float) * num_files)) || + 0 == (title_peaks = (float*)malloc(sizeof(float) * num_files)) + ) + die("out of memory allocating space for title gains/peaks"); + + for(i = 0; i < num_files; i++) { + if(!do_replaygain_analyze_file(filenames[i], title_gains+i, title_peaks+i)) { + free(title_gains); + free(title_peaks); + return false; + } + if(title_peaks[i] > album_peak) + album_peak = title_peaks[i]; + } + album_gain = FLAC__replaygain_get_album_gain(); + + for(i = 0; i < num_files; i++) { + if(!do_replaygain_store_to_file(filenames[i], album_gain, album_peak, title_gains[i], title_peaks[i])) { + free(title_gains); + free(title_peaks); + return false; + } + } + + free(title_gains); + free(title_peaks); + return true; +} + +FLAC__bool do_replaygain_analyze_file(const char *filename, float *title_gain, float *title_peak) +{ + return false;//@@@@ +} + +FLAC__bool do_replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak) +{ + return false;//@@@@ +} + FLAC__bool do_shorthand_operation__add_padding(const char *filename, FLAC__Metadata_Chain *chain, unsigned length, FLAC__bool *needs_write) { FLAC__StreamMetadata *padding = 0; diff --git a/src/metaflac/metaflac.dsp b/src/metaflac/metaflac.dsp index 0aab5866..9f4b5a3e 100644 --- a/src/metaflac/metaflac.dsp +++ b/src/metaflac/metaflac.dsp @@ -51,7 +51,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 -# ADD LINK32 ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\replaygain.lib ..\..\obj\lib\gain_analysis.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /machine:I386 !ELSEIF "$(CFG)" == "metaflac - Win32 Debug" @@ -76,7 +76,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept -# ADD LINK32 ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ..\..\obj\lib\libFLAC.lib ..\..\obj\lib\replaygain.lib ..\..\obj\lib\gain_analysis.lib ..\..\obj\lib\getopt.lib ..\..\obj\lib\utf8.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept !ENDIF