/* 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)
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)
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;
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;
}
}
-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;
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";
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);
{
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;
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";
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";
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);
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;
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)
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;
}