ce188d4d |
1 | /* grabbag - Convenience lib for various routines common to several tools |
2 | * Copyright (C) 2002-2009 Josh Coalson |
3 | * Copyright (C) 2011-2016 Xiph.Org Foundation |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library; if not, write to the Free Software |
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
18 | */ |
19 | |
20 | #ifdef HAVE_CONFIG_H |
21 | # include <config.h> |
22 | #endif |
23 | |
24 | #include <locale.h> |
25 | #include <math.h> |
26 | #include <stdio.h> |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | #if defined _MSC_VER || defined __MINGW32__ |
30 | #include <io.h> /* for chmod() */ |
31 | #endif |
32 | #include <sys/stat.h> /* for stat(), maybe chmod() */ |
33 | |
34 | #include "FLAC/assert.h" |
35 | #include "FLAC/metadata.h" |
36 | #include "FLAC/stream_decoder.h" |
37 | #include "share/grabbag.h" |
38 | #include "share/replaygain_analysis.h" |
39 | #include "share/safe_str.h" |
40 | |
41 | #ifdef local_min |
42 | #undef local_min |
43 | #endif |
44 | #define local_min(a,b) ((a)<(b)?(a):(b)) |
45 | |
46 | #ifdef local_max |
47 | #undef local_max |
48 | #endif |
49 | #define local_max(a,b) ((a)>(b)?(a):(b)) |
50 | |
51 | static const char *reference_format_ = "%s=%2.1f dB"; |
52 | static const char *gain_format_ = "%s=%+2.2f dB"; |
53 | static const char *peak_format_ = "%s=%1.8f"; |
54 | |
55 | static double album_peak_, title_peak_; |
56 | |
57 | const unsigned GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED = 190; |
58 | /* |
59 | FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 29 + 1 + 8 + |
60 | FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + |
61 | FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 + |
62 | FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + |
63 | FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 |
64 | */ |
65 | |
66 | const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS = (const FLAC__byte * const)"REPLAYGAIN_REFERENCE_LOUDNESS"; |
67 | const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN = (const FLAC__byte * const)"REPLAYGAIN_TRACK_GAIN"; |
68 | const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK = (const FLAC__byte * const)"REPLAYGAIN_TRACK_PEAK"; |
69 | const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_GAIN"; |
70 | const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_PEAK"; |
71 | |
72 | |
73 | static FLAC__bool get_file_stats_(const char *filename, struct flac_stat_s *stats) |
74 | { |
75 | FLAC__ASSERT(0 != filename); |
76 | FLAC__ASSERT(0 != stats); |
77 | return (0 == flac_stat(filename, stats)); |
78 | } |
79 | |
80 | static void set_file_stats_(const char *filename, struct flac_stat_s *stats) |
81 | { |
82 | FLAC__ASSERT(0 != filename); |
83 | FLAC__ASSERT(0 != stats); |
84 | |
85 | (void)flac_chmod(filename, stats->st_mode); |
86 | } |
87 | |
88 | static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, const FLAC__byte *name, float value) |
89 | { |
90 | char buffer[256]; |
91 | char *saved_locale; |
92 | FLAC__StreamMetadata_VorbisComment_Entry entry; |
93 | |
94 | FLAC__ASSERT(0 != block); |
95 | FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); |
96 | FLAC__ASSERT(0 != format); |
97 | FLAC__ASSERT(0 != name); |
98 | |
99 | buffer[sizeof(buffer)-1] = '\0'; |
100 | /* |
101 | * We need to save the old locale and switch to "C" because the locale |
102 | * influences the formatting of %f and we want it a certain way. |
103 | */ |
104 | saved_locale = strdup(setlocale(LC_ALL, 0)); |
105 | if (0 == saved_locale) |
106 | return false; |
107 | setlocale(LC_ALL, "C"); |
108 | flac_snprintf(buffer, sizeof(buffer), format, name, value); |
109 | setlocale(LC_ALL, saved_locale); |
110 | free(saved_locale); |
111 | |
112 | entry.entry = (FLAC__byte *)buffer; |
113 | entry.length = strlen(buffer); |
114 | |
115 | return FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true); |
116 | } |
117 | |
118 | FLAC__bool grabbag__replaygain_is_valid_sample_frequency(unsigned sample_frequency) |
119 | { |
120 | return ValidGainFrequency( sample_frequency ); |
121 | } |
122 | |
123 | FLAC__bool grabbag__replaygain_init(unsigned sample_frequency) |
124 | { |
125 | title_peak_ = album_peak_ = 0.0; |
126 | return InitGainAnalysis((long)sample_frequency) == INIT_GAIN_ANALYSIS_OK; |
127 | } |
128 | |
129 | FLAC__bool grabbag__replaygain_analyze(const FLAC__int32 * const input[], FLAC__bool is_stereo, unsigned bps, unsigned samples) |
130 | { |
131 | /* using a small buffer improves data locality; we'd like it to fit easily in the dcache */ |
132 | static flac_float_t lbuffer[2048], rbuffer[2048]; |
133 | static const unsigned nbuffer = sizeof(lbuffer) / sizeof(lbuffer[0]); |
134 | FLAC__int32 block_peak = 0, s; |
135 | unsigned i, j; |
136 | |
137 | FLAC__ASSERT(bps >= 4 && bps <= FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE); |
138 | FLAC__ASSERT(FLAC__MIN_BITS_PER_SAMPLE == 4); |
139 | /* |
140 | * We use abs() on a FLAC__int32 which is undefined for the most negative value. |
141 | * If the reference codec ever handles 32bps we will have to write a special |
142 | * case here. |
143 | */ |
144 | FLAC__ASSERT(FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE < 32); |
145 | |
146 | if(bps == 16) { |
147 | if(is_stereo) { |
148 | j = 0; |
149 | while(samples > 0) { |
150 | const unsigned n = local_min(samples, nbuffer); |
151 | for(i = 0; i < n; i++, j++) { |
152 | s = input[0][j]; |
153 | lbuffer[i] = (flac_float_t)s; |
154 | s = abs(s); |
155 | block_peak = local_max(block_peak, s); |
156 | |
157 | s = input[1][j]; |
158 | rbuffer[i] = (flac_float_t)s; |
159 | s = abs(s); |
160 | block_peak = local_max(block_peak, s); |
161 | } |
162 | samples -= n; |
163 | if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) |
164 | return false; |
165 | } |
166 | } |
167 | else { |
168 | j = 0; |
169 | while(samples > 0) { |
170 | const unsigned n = local_min(samples, nbuffer); |
171 | for(i = 0; i < n; i++, j++) { |
172 | s = input[0][j]; |
173 | lbuffer[i] = (flac_float_t)s; |
174 | s = abs(s); |
175 | block_peak = local_max(block_peak, s); |
176 | } |
177 | samples -= n; |
178 | if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) |
179 | return false; |
180 | } |
181 | } |
182 | } |
183 | else { /* bps must be < 32 according to above assertion */ |
184 | const double scale = ( |
185 | (bps > 16)? |
186 | (double)1. / (double)(1u << (bps - 16)) : |
187 | (double)(1u << (16 - bps)) |
188 | ); |
189 | |
190 | if(is_stereo) { |
191 | j = 0; |
192 | while(samples > 0) { |
193 | const unsigned n = local_min(samples, nbuffer); |
194 | for(i = 0; i < n; i++, j++) { |
195 | s = input[0][j]; |
196 | lbuffer[i] = (flac_float_t)(scale * (double)s); |
197 | s = abs(s); |
198 | block_peak = local_max(block_peak, s); |
199 | |
200 | s = input[1][j]; |
201 | rbuffer[i] = (flac_float_t)(scale * (double)s); |
202 | s = abs(s); |
203 | block_peak = local_max(block_peak, s); |
204 | } |
205 | samples -= n; |
206 | if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) |
207 | return false; |
208 | } |
209 | } |
210 | else { |
211 | j = 0; |
212 | while(samples > 0) { |
213 | const unsigned n = local_min(samples, nbuffer); |
214 | for(i = 0; i < n; i++, j++) { |
215 | s = input[0][j]; |
216 | lbuffer[i] = (flac_float_t)(scale * (double)s); |
217 | s = abs(s); |
218 | block_peak = local_max(block_peak, s); |
219 | } |
220 | samples -= n; |
221 | if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) |
222 | return false; |
223 | } |
224 | } |
225 | } |
226 | |
227 | { |
228 | const double peak_scale = (double)(1u << (bps - 1)); |
229 | double peak = (double)block_peak / peak_scale; |
230 | if(peak > title_peak_) |
231 | title_peak_ = peak; |
232 | if(peak > album_peak_) |
233 | album_peak_ = peak; |
234 | } |
235 | |
236 | return true; |
237 | } |
238 | |
239 | void grabbag__replaygain_get_album(float *gain, float *peak) |
240 | { |
241 | *gain = (float)GetAlbumGain(); |
242 | *peak = (float)album_peak_; |
243 | album_peak_ = 0.0; |
244 | } |
245 | |
246 | void grabbag__replaygain_get_title(float *gain, float *peak) |
247 | { |
248 | *gain = (float)GetTitleGain(); |
249 | *peak = (float)title_peak_; |
250 | title_peak_ = 0.0; |
251 | } |
252 | |
253 | |
254 | typedef struct { |
255 | unsigned channels; |
256 | unsigned bits_per_sample; |
257 | unsigned sample_rate; |
258 | FLAC__bool error; |
259 | } DecoderInstance; |
260 | |
261 | static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) |
262 | { |
263 | DecoderInstance *instance = (DecoderInstance*)client_data; |
264 | const unsigned bits_per_sample = frame->header.bits_per_sample; |
265 | const unsigned channels = frame->header.channels; |
266 | const unsigned sample_rate = frame->header.sample_rate; |
267 | const unsigned samples = frame->header.blocksize; |
268 | |
269 | (void)decoder; |
270 | |
271 | if( |
272 | !instance->error && |
273 | (channels == 2 || channels == 1) && |
274 | bits_per_sample == instance->bits_per_sample && |
275 | channels == instance->channels && |
276 | sample_rate == instance->sample_rate |
277 | ) { |
278 | instance->error = !grabbag__replaygain_analyze(buffer, channels==2, bits_per_sample, samples); |
279 | } |
280 | else { |
281 | instance->error = true; |
282 | } |
283 | |
284 | if(!instance->error) |
285 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
286 | else |
287 | return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; |
288 | } |
289 | |
290 | static void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) |
291 | { |
292 | DecoderInstance *instance = (DecoderInstance*)client_data; |
293 | |
294 | (void)decoder; |
295 | |
296 | if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { |
297 | instance->bits_per_sample = metadata->data.stream_info.bits_per_sample; |
298 | instance->channels = metadata->data.stream_info.channels; |
299 | instance->sample_rate = metadata->data.stream_info.sample_rate; |
300 | |
301 | if(instance->channels != 1 && instance->channels != 2) { |
302 | instance->error = true; |
303 | return; |
304 | } |
305 | |
306 | if(!grabbag__replaygain_is_valid_sample_frequency(instance->sample_rate)) { |
307 | instance->error = true; |
308 | return; |
309 | } |
310 | } |
311 | } |
312 | |
313 | static void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) |
314 | { |
315 | DecoderInstance *instance = (DecoderInstance*)client_data; |
316 | |
317 | (void)decoder, (void)status; |
318 | |
319 | instance->error = true; |
320 | } |
321 | |
322 | const char *grabbag__replaygain_analyze_file(const char *filename, float *title_gain, float *title_peak) |
323 | { |
324 | DecoderInstance instance; |
325 | FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); |
326 | |
327 | if(0 == decoder) |
328 | return "memory allocation error"; |
329 | |
330 | instance.error = false; |
331 | |
332 | /* It does these three by default but lets be explicit: */ |
333 | FLAC__stream_decoder_set_md5_checking(decoder, false); |
334 | FLAC__stream_decoder_set_metadata_ignore_all(decoder); |
335 | FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO); |
336 | |
337 | if(FLAC__stream_decoder_init_file(decoder, filename, write_callback_, metadata_callback_, error_callback_, &instance) != FLAC__STREAM_DECODER_INIT_STATUS_OK) { |
338 | FLAC__stream_decoder_delete(decoder); |
339 | return "initializing decoder"; |
340 | } |
341 | |
342 | if(!FLAC__stream_decoder_process_until_end_of_stream(decoder) || instance.error) { |
343 | FLAC__stream_decoder_delete(decoder); |
344 | return "decoding file"; |
345 | } |
346 | |
347 | FLAC__stream_decoder_delete(decoder); |
348 | |
349 | grabbag__replaygain_get_title(title_gain, title_peak); |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | const char *grabbag__replaygain_store_to_vorbiscomment(FLAC__StreamMetadata *block, float album_gain, float album_peak, float title_gain, float title_peak) |
355 | { |
356 | const char *error; |
357 | |
358 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) |
359 | return error; |
360 | |
361 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) |
362 | return error; |
363 | |
364 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) |
365 | return error; |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | const char *grabbag__replaygain_store_to_vorbiscomment_reference(FLAC__StreamMetadata *block) |
371 | { |
372 | FLAC__ASSERT(0 != block); |
373 | FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); |
374 | |
375 | if(FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS) < 0) |
376 | return "memory allocation error"; |
377 | |
378 | if(!append_tag_(block, reference_format_, GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS, ReplayGainReferenceLoudness)) |
379 | return "memory allocation error"; |
380 | |
381 | return 0; |
382 | } |
383 | |
384 | const char *grabbag__replaygain_store_to_vorbiscomment_album(FLAC__StreamMetadata *block, float album_gain, float album_peak) |
385 | { |
386 | FLAC__ASSERT(0 != block); |
387 | FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); |
388 | |
389 | if( |
390 | FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN) < 0 || |
391 | FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK) < 0 |
392 | ) |
393 | return "memory allocation error"; |
394 | |
395 | if( |
396 | !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN, album_gain) || |
397 | !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK, album_peak) |
398 | ) |
399 | return "memory allocation error"; |
400 | |
401 | return 0; |
402 | } |
403 | |
404 | const char *grabbag__replaygain_store_to_vorbiscomment_title(FLAC__StreamMetadata *block, float title_gain, float title_peak) |
405 | { |
406 | FLAC__ASSERT(0 != block); |
407 | FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); |
408 | |
409 | if( |
410 | FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN) < 0 || |
411 | FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK) < 0 |
412 | ) |
413 | return "memory allocation error"; |
414 | |
415 | if( |
416 | !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN, title_gain) || |
417 | !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK, title_peak) |
418 | ) |
419 | return "memory allocation error"; |
420 | |
421 | return 0; |
422 | } |
423 | |
424 | static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain **chain, FLAC__StreamMetadata **block) |
425 | { |
426 | FLAC__Metadata_Iterator *iterator; |
427 | const char *error; |
428 | FLAC__bool found_vc_block = false; |
429 | |
430 | if(0 == (*chain = FLAC__metadata_chain_new())) |
431 | return "memory allocation error"; |
432 | |
433 | if(!FLAC__metadata_chain_read(*chain, filename)) { |
434 | error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; |
435 | FLAC__metadata_chain_delete(*chain); |
436 | return error; |
437 | } |
438 | |
439 | if(0 == (iterator = FLAC__metadata_iterator_new())) { |
440 | FLAC__metadata_chain_delete(*chain); |
441 | return "memory allocation error"; |
442 | } |
443 | |
444 | FLAC__metadata_iterator_init(iterator, *chain); |
445 | |
446 | do { |
447 | *block = FLAC__metadata_iterator_get_block(iterator); |
448 | if((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) |
449 | found_vc_block = true; |
450 | } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); |
451 | |
452 | if(!found_vc_block) { |
453 | /* create a new block */ |
454 | *block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); |
455 | if(0 == *block) { |
456 | FLAC__metadata_chain_delete(*chain); |
457 | FLAC__metadata_iterator_delete(iterator); |
458 | return "memory allocation error"; |
459 | } |
460 | while(FLAC__metadata_iterator_next(iterator)) |
461 | ; |
462 | if(!FLAC__metadata_iterator_insert_block_after(iterator, *block)) { |
463 | error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; |
464 | FLAC__metadata_chain_delete(*chain); |
465 | FLAC__metadata_iterator_delete(iterator); |
466 | return error; |
467 | } |
468 | /* iterator is left pointing to new block */ |
469 | FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == *block); |
470 | } |
471 | |
472 | FLAC__metadata_iterator_delete(iterator); |
473 | |
474 | FLAC__ASSERT(0 != *block); |
475 | FLAC__ASSERT((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); |
476 | |
477 | return 0; |
478 | } |
479 | |
480 | static const char *store_to_file_post_(const char *filename, FLAC__Metadata_Chain *chain, FLAC__bool preserve_modtime) |
481 | { |
482 | struct flac_stat_s stats; |
483 | const FLAC__bool have_stats = get_file_stats_(filename, &stats); |
484 | |
485 | (void)grabbag__file_change_stats(filename, /*read_only=*/false); |
486 | |
487 | FLAC__metadata_chain_sort_padding(chain); |
488 | if(!FLAC__metadata_chain_write(chain, /*use_padding=*/true, preserve_modtime)) { |
489 | const char *error; |
490 | error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]; |
491 | FLAC__metadata_chain_delete(chain); |
492 | return error; |
493 | } |
494 | |
495 | FLAC__metadata_chain_delete(chain); |
496 | |
497 | if(have_stats) |
498 | set_file_stats_(filename, &stats); |
499 | |
500 | return 0; |
501 | } |
502 | |
503 | const char *grabbag__replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak, FLAC__bool preserve_modtime) |
504 | { |
505 | FLAC__Metadata_Chain *chain; |
506 | FLAC__StreamMetadata *block = NULL; |
507 | const char *error; |
508 | |
509 | if(0 != (error = store_to_file_pre_(filename, &chain, &block))) |
510 | return error; |
511 | |
512 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment(block, album_gain, album_peak, title_gain, title_peak))) { |
513 | FLAC__metadata_chain_delete(chain); |
514 | return error; |
515 | } |
516 | |
517 | if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) |
518 | return error; |
519 | |
520 | return 0; |
521 | } |
522 | |
523 | const char *grabbag__replaygain_store_to_file_reference(const char *filename, FLAC__bool preserve_modtime) |
524 | { |
525 | FLAC__Metadata_Chain *chain; |
526 | FLAC__StreamMetadata *block = NULL; |
527 | const char *error; |
528 | |
529 | if(0 != (error = store_to_file_pre_(filename, &chain, &block))) |
530 | return error; |
531 | |
532 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) { |
533 | FLAC__metadata_chain_delete(chain); |
534 | return error; |
535 | } |
536 | |
537 | if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) |
538 | return error; |
539 | |
540 | return 0; |
541 | } |
542 | |
543 | const char *grabbag__replaygain_store_to_file_album(const char *filename, float album_gain, float album_peak, FLAC__bool preserve_modtime) |
544 | { |
545 | FLAC__Metadata_Chain *chain; |
546 | FLAC__StreamMetadata *block = NULL; |
547 | const char *error; |
548 | |
549 | if(0 != (error = store_to_file_pre_(filename, &chain, &block))) |
550 | return error; |
551 | |
552 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) { |
553 | FLAC__metadata_chain_delete(chain); |
554 | return error; |
555 | } |
556 | |
557 | if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) |
558 | return error; |
559 | |
560 | return 0; |
561 | } |
562 | |
563 | const char *grabbag__replaygain_store_to_file_title(const char *filename, float title_gain, float title_peak, FLAC__bool preserve_modtime) |
564 | { |
565 | FLAC__Metadata_Chain *chain; |
566 | FLAC__StreamMetadata *block = NULL; |
567 | const char *error; |
568 | |
569 | if(0 != (error = store_to_file_pre_(filename, &chain, &block))) |
570 | return error; |
571 | |
572 | if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) { |
573 | FLAC__metadata_chain_delete(chain); |
574 | return error; |
575 | } |
576 | |
577 | if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) |
578 | return error; |
579 | |
580 | return 0; |
581 | } |
582 | |
583 | static FLAC__bool parse_double_(const FLAC__StreamMetadata_VorbisComment_Entry *entry, double *val) |
584 | { |
585 | char s[32], *end; |
586 | const char *p, *q; |
587 | double v; |
588 | |
589 | FLAC__ASSERT(0 != entry); |
590 | FLAC__ASSERT(0 != val); |
591 | |
592 | p = (const char *)entry->entry; |
593 | q = strchr(p, '='); |
594 | if(0 == q) |
595 | return false; |
596 | q++; |
597 | safe_strncpy(s, q, local_min(sizeof(s), (size_t) (entry->length - (q-p)))); |
598 | |
599 | v = strtod(s, &end); |
600 | if(end == s) |
601 | return false; |
602 | |
603 | *val = v; |
604 | return true; |
605 | } |
606 | |
607 | FLAC__bool grabbag__replaygain_load_from_vorbiscomment(const FLAC__StreamMetadata *block, FLAC__bool album_mode, FLAC__bool strict, double *reference, double *gain, double *peak) |
608 | { |
609 | int reference_offset, gain_offset, peak_offset; |
610 | char *saved_locale; |
611 | FLAC__bool res = true; |
612 | |
613 | FLAC__ASSERT(0 != block); |
614 | FLAC__ASSERT(0 != reference); |
615 | FLAC__ASSERT(0 != gain); |
616 | FLAC__ASSERT(0 != peak); |
617 | FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); |
618 | |
619 | /* Default to current level until overridden by a detected tag; this |
620 | * will always be true until we change replaygain_analysis.c |
621 | */ |
622 | *reference = ReplayGainReferenceLoudness; |
623 | |
624 | /* |
625 | * We need to save the old locale and switch to "C" because the locale |
626 | * influences the behaviour of strtod and we want it a certain way. |
627 | */ |
628 | saved_locale = strdup(setlocale(LC_ALL, 0)); |
629 | if (0 == saved_locale) |
630 | return false; |
631 | setlocale(LC_ALL, "C"); |
632 | |
633 | if(0 <= (reference_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS))) |
634 | (void)parse_double_(block->data.vorbis_comment.comments + reference_offset, reference); |
635 | |
636 | 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)))) |
637 | res = false; |
638 | 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)))) |
639 | res = false; |
640 | |
641 | if(res && !parse_double_(block->data.vorbis_comment.comments + gain_offset, gain)) |
642 | res = false; |
643 | if(res && !parse_double_(block->data.vorbis_comment.comments + peak_offset, peak)) |
644 | res = false; |
645 | |
646 | setlocale(LC_ALL, saved_locale); |
647 | free(saved_locale); |
648 | |
649 | /* something failed; retry with strict */ |
650 | if (!res && !strict) |
651 | res = grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak); |
652 | |
653 | return res; |
654 | } |
655 | |
656 | double grabbag__replaygain_compute_scale_factor(double peak, double gain, double preamp, FLAC__bool prevent_clipping) |
657 | { |
658 | double scale; |
659 | FLAC__ASSERT(peak >= 0.0); |
660 | gain += preamp; |
661 | scale = (float) pow(10.0, gain * 0.05); |
662 | if(prevent_clipping && peak > 0.0) { |
663 | const double max_scale = (float)(1.0 / peak); |
664 | if(scale > max_scale) |
665 | scale = max_scale; |
666 | } |
667 | return scale; |
668 | } |