Rename _flac_stat to flac_stat_s.
[platform/upstream/flac.git] / src / share / grabbag / replaygain.c
index 61db0c1..43f1be0 100644 (file)
@@ -1,26 +1,25 @@
 /* grabbag - Convenience lib for various routines common to several tools
- * Copyright (C) 2002,2003  Josh Coalson
+ * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009  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 library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This library 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.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser 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.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-#include "share/grabbag.h"
-#include "share/replaygain_analysis.h"
-#include "FLAC/assert.h"
-#include "FLAC/file_decoder.h"
-#include "FLAC/metadata.h"
+#if HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
 #include <locale.h>
 #include <math.h>
 #include <stdio.h>
 #endif
 #include <sys/stat.h> /* for stat(), maybe chmod() */
 
+#include "FLAC/assert.h"
+#include "FLAC/metadata.h"
+#include "FLAC/stream_decoder.h"
+#include "share/grabbag.h"
+#include "share/replaygain_analysis.h"
+#include "share/safe_str.h"
+
 #ifdef local_min
 #undef local_min
 #endif
 #endif
 #define local_max(a,b) ((a)>(b)?(a):(b))
 
-static const FLAC__byte *tag_title_gain_ = "REPLAYGAIN_TRACK_GAIN";
-static const FLAC__byte *tag_title_peak_ = "REPLAYGAIN_TRACK_PEAK";
-static const FLAC__byte *tag_album_gain_ = "REPLAYGAIN_ALBUM_GAIN";
-static const FLAC__byte *tag_album_peak_ = "REPLAYGAIN_ALBUM_PEAK";
-static const char *peak_format_ = "%s=%1.8f";
+static const char *reference_format_ = "%s=%2.1f dB";
 static const char *gain_format_ = "%s=%+2.2f dB";
+static const char *peak_format_ = "%s=%1.8f";
 
 static double album_peak_, title_peak_;
 
-const unsigned GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED = 148;
+const unsigned GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED = 190;
 /*
+       FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 29 + 1 + 8 +
        FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 +
        FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 +
        FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 +
        FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12
 */
 
+const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS = (const FLAC__byte * const)"REPLAYGAIN_REFERENCE_LOUDNESS";
+const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN = (const FLAC__byte * const)"REPLAYGAIN_TRACK_GAIN";
+const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK = (const FLAC__byte * const)"REPLAYGAIN_TRACK_PEAK";
+const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_GAIN";
+const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_PEAK";
+
 
-static FLAC__bool get_file_stats_(const char *filename, struct stat *stats)
+static FLAC__bool get_file_stats_(const char *filename, struct flac_stat_s *stats)
 {
        FLAC__ASSERT(0 != filename);
        FLAC__ASSERT(0 != stats);
-       return (0 == stat(filename, stats));
+       return (0 == flac_stat(filename, stats));
 }
 
-static void set_file_stats_(const char *filename, struct stat *stats)
+static void set_file_stats_(const char *filename, struct flac_stat_s *stats)
 {
        FLAC__ASSERT(0 != filename);
        FLAC__ASSERT(0 != stats);
 
-       (void)chmod(filename, stats->st_mode);
+       (void)flac_chmod(filename, stats->st_mode);
 }
 
 static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, const FLAC__byte *name, float value)
@@ -82,50 +92,31 @@ static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, c
 
        FLAC__ASSERT(0 != block);
        FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
+       FLAC__ASSERT(0 != format);
        FLAC__ASSERT(0 != name);
-       FLAC__ASSERT(0 != value);
 
        buffer[sizeof(buffer)-1] = '\0';
        /*
         * We need to save the old locale and switch to "C" because the locale
         * influences the formatting of %f and we want it a certain way.
         */
-       saved_locale = setlocale(LC_ALL, 0);
+       saved_locale = strdup(setlocale(LC_ALL, 0));
+       if (0 == saved_locale)
+               return false;
        setlocale(LC_ALL, "C");
-#if defined _MSC_VER || defined __MINGW32__
-       _snprintf(buffer, sizeof(buffer)-1, format, name, value);
-#else
-       snprintf(buffer, sizeof(buffer)-1, format, name, value);
-#endif
+       flac_snprintf(buffer, sizeof(buffer), format, name, value);
        setlocale(LC_ALL, saved_locale);
+       free(saved_locale);
 
        entry.entry = (FLAC__byte *)buffer;
        entry.length = strlen(buffer);
 
-       return FLAC__metadata_object_vorbiscomment_insert_comment(block, block->data.vorbis_comment.num_comments, entry, /*copy=*/true);
+       return FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true);
 }
 
 FLAC__bool grabbag__replaygain_is_valid_sample_frequency(unsigned sample_frequency)
 {
-       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]);
-
-       unsigned i;
-
-       for(i = 0; i < n_valid_sample_rates; i++)
-               if(sample_frequency == valid_sample_rates[i])
-                       return true;
-       return false;
+        return ValidGainFrequency( sample_frequency );
 }
 
 FLAC__bool grabbag__replaygain_init(unsigned sample_frequency)
@@ -266,7 +257,7 @@ typedef struct {
        FLAC__bool error;
 } DecoderInstance;
 
-static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__FileDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
+static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
 {
        DecoderInstance *instance = (DecoderInstance*)client_data;
        const unsigned bits_per_sample = frame->header.bits_per_sample;
@@ -295,7 +286,7 @@ static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__FileDecoder *d
                return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
 }
 
-static void metadata_callback_(const FLAC__FileDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
+static void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
 {
        DecoderInstance *instance = (DecoderInstance*)client_data;
 
@@ -318,7 +309,7 @@ static void metadata_callback_(const FLAC__FileDecoder *decoder, const FLAC__Str
        }
 }
 
-static void error_callback_(const FLAC__FileDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
+static void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
 {
        DecoderInstance *instance = (DecoderInstance*)client_data;
 
@@ -330,7 +321,7 @@ static void error_callback_(const FLAC__FileDecoder *decoder, FLAC__StreamDecode
 const char *grabbag__replaygain_analyze_file(const char *filename, float *title_gain, float *title_peak)
 {
        DecoderInstance instance;
-       FLAC__FileDecoder *decoder = FLAC__file_decoder_new();
+       FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new();
 
        if(0 == decoder)
                return "memory allocation error";
@@ -338,27 +329,21 @@ const char *grabbag__replaygain_analyze_file(const char *filename, float *title_
        instance.error = false;
 
        /* It does these three by default but lets be explicit: */
-       FLAC__file_decoder_set_md5_checking(decoder, false);
-       FLAC__file_decoder_set_metadata_ignore_all(decoder);
-       FLAC__file_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO);
-
-       FLAC__file_decoder_set_filename(decoder, filename);
-       FLAC__file_decoder_set_write_callback(decoder, write_callback_);
-       FLAC__file_decoder_set_metadata_callback(decoder, metadata_callback_);
-       FLAC__file_decoder_set_error_callback(decoder, error_callback_);
-       FLAC__file_decoder_set_client_data(decoder, &instance);
-
-       if(FLAC__file_decoder_init(decoder) != FLAC__FILE_DECODER_OK) {
-               FLAC__file_decoder_delete(decoder);
+       FLAC__stream_decoder_set_md5_checking(decoder, false);
+       FLAC__stream_decoder_set_metadata_ignore_all(decoder);
+       FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO);
+
+       if(FLAC__stream_decoder_init_file(decoder, filename, write_callback_, metadata_callback_, error_callback_, &instance) != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+               FLAC__stream_decoder_delete(decoder);
                return "initializing decoder";
        }
 
-       if(!FLAC__file_decoder_process_until_end_of_file(decoder) || instance.error) {
-               FLAC__file_decoder_delete(decoder);
+       if(!FLAC__stream_decoder_process_until_end_of_stream(decoder) || instance.error) {
+               FLAC__stream_decoder_delete(decoder);
                return "decoding file";
        }
 
-       FLAC__file_decoder_delete(decoder);
+       FLAC__stream_decoder_delete(decoder);
 
        grabbag__replaygain_get_title(title_gain, title_peak);
 
@@ -369,6 +354,9 @@ const char *grabbag__replaygain_store_to_vorbiscomment(FLAC__StreamMetadata *blo
 {
        const char *error;
 
+       if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block)))
+               return error;
+
        if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak)))
                return error;
 
@@ -378,20 +366,34 @@ const char *grabbag__replaygain_store_to_vorbiscomment(FLAC__StreamMetadata *blo
        return 0;
 }
 
+const char *grabbag__replaygain_store_to_vorbiscomment_reference(FLAC__StreamMetadata *block)
+{
+       FLAC__ASSERT(0 != block);
+       FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
+
+       if(FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS) < 0)
+               return "memory allocation error";
+
+       if(!append_tag_(block, reference_format_, GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS, ReplayGainReferenceLoudness))
+               return "memory allocation error";
+
+       return 0;
+}
+
 const char *grabbag__replaygain_store_to_vorbiscomment_album(FLAC__StreamMetadata *block, float album_gain, float album_peak)
 {
        FLAC__ASSERT(0 != block);
        FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
 
        if(
-               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)tag_album_gain_) < 0 ||
-               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)tag_album_peak_) < 0
+               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN) < 0 ||
+               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK) < 0
        )
                return "memory allocation error";
 
        if(
-               !append_tag_(block, peak_format_, tag_album_peak_, album_peak) ||
-               !append_tag_(block, gain_format_, tag_album_gain_, album_gain)
+               !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN, album_gain) ||
+               !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK, album_peak)
        )
                return "memory allocation error";
 
@@ -404,14 +406,14 @@ const char *grabbag__replaygain_store_to_vorbiscomment_title(FLAC__StreamMetadat
        FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
 
        if(
-               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)tag_title_gain_) < 0 ||
-               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)tag_title_peak_) < 0
+               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN) < 0 ||
+               FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK) < 0
        )
                return "memory allocation error";
 
        if(
-               !append_tag_(block, peak_format_, tag_title_peak_, title_peak) ||
-               !append_tag_(block, gain_format_, tag_title_gain_, title_gain)
+               !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN, title_gain) ||
+               !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK, title_peak)
        )
                return "memory allocation error";
 
@@ -476,15 +478,17 @@ static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain
 
 static const char *store_to_file_post_(const char *filename, FLAC__Metadata_Chain *chain, FLAC__bool preserve_modtime)
 {
-       struct stat stats;
+       struct flac_stat_s stats;
        const FLAC__bool have_stats = get_file_stats_(filename, &stats);
 
        (void)grabbag__file_change_stats(filename, /*read_only=*/false);
 
        FLAC__metadata_chain_sort_padding(chain);
        if(!FLAC__metadata_chain_write(chain, /*use_padding=*/true, preserve_modtime)) {
+               const char *error;
+               error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)];
                FLAC__metadata_chain_delete(chain);
-               return FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)];
+               return error;
        }
 
        FLAC__metadata_chain_delete(chain);
@@ -515,6 +519,26 @@ const char *grabbag__replaygain_store_to_file(const char *filename, float album_
        return 0;
 }
 
+const char *grabbag__replaygain_store_to_file_reference(const char *filename, FLAC__bool preserve_modtime)
+{
+       FLAC__Metadata_Chain *chain;
+       FLAC__StreamMetadata *block;
+       const char *error;
+
+       if(0 != (error = store_to_file_pre_(filename, &chain, &block)))
+               return error;
+
+       if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) {
+               FLAC__metadata_chain_delete(chain);
+               return error;
+       }
+
+       if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime)))
+               return error;
+
+       return 0;
+}
+
 const char *grabbag__replaygain_store_to_file_album(const char *filename, float album_gain, float album_peak, FLAC__bool preserve_modtime)
 {
        FLAC__Metadata_Chain *chain;
@@ -569,8 +593,7 @@ static FLAC__bool parse_double_(const FLAC__StreamMetadata_VorbisComment_Entry *
        if(0 == q)
                return false;
        q++;
-       memset(s, 0, sizeof(s)-1);
-       strncpy(s, q, local_min(sizeof(s)-1, entry->length - (q-p)));
+       safe_strncpy(s, q, local_min(sizeof(s), (size_t) (entry->length - (q-p))));
 
        v = strtod(s, &end);
        if(end == s)
@@ -580,22 +603,33 @@ static FLAC__bool parse_double_(const FLAC__StreamMetadata_VorbisComment_Entry *
        return true;
 }
 
-FLAC__bool grabbag__replaygain_load_from_vorbiscomment(const FLAC__StreamMetadata *block, FLAC__bool album_mode, double *gain, double *peak)
+FLAC__bool grabbag__replaygain_load_from_vorbiscomment(const FLAC__StreamMetadata *block, FLAC__bool album_mode, FLAC__bool strict, double *reference, double *gain, double *peak)
 {
-       int gain_offset, peak_offset;
+       int reference_offset, gain_offset, peak_offset;
 
        FLAC__ASSERT(0 != block);
+       FLAC__ASSERT(0 != reference);
+       FLAC__ASSERT(0 != gain);
+       FLAC__ASSERT(0 != peak);
        FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
 
-       if(0 > (gain_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? tag_album_gain_ : tag_title_gain_))))
-               return false;
-       if(0 > (peak_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? tag_album_peak_ : tag_title_peak_))))
-               return false;
+       /* Default to current level until overridden by a detected tag; this
+        * will always be true until we change replaygain_analysis.c
+        */
+       *reference = ReplayGainReferenceLoudness;
+
+       if(0 <= (reference_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS)))
+               (void)parse_double_(block->data.vorbis_comment.comments + reference_offset, reference);
+
+       if(0 > (gain_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN : GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN))))
+               return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak);
+       if(0 > (peak_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK : GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK))))
+               return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak);
 
        if(!parse_double_(block->data.vorbis_comment.comments + gain_offset, gain))
-               return false;
+               return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak);
        if(!parse_double_(block->data.vorbis_comment.comments + peak_offset, peak))
-               return false;
+               return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak);
 
        return true;
 }