1 /*
2  * function: ReplayGain support for Wave files (http://www.replaygain.org)
3  *
4  * Reads in a Vorbis file, figures out the peak and ReplayGain levels and
5  * applies the Gain tags to the Wave file
6  *
7  * This program is distributed under the GNU General Public License, version
8  * 2.1. A copy of this license is included with this source.
9  *
10  * Copyright (C) 2002-2004 John Edwards
11  * Additional code by Magnus Holmgren and Gian-Carlo Pascutto
12  * Linux patch by Marc Brooker
13  */
14 
15 #include "config.h"
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <math.h>
19 #include <string.h>
20 #include <ctype.h>
21 
22 #include <fcntl.h>
23 
24 #include "gain_analysis.h"
25 #include "i18n.h"
26 #include "getopt.h"
27 #include "misc.h"
28 #include "audio.h"
29 #include "dither.h"
30 #include "main.h"
31 #include "wavegain.h"
32 
33 #ifdef _WIN32
34 #include <windows.h>
35 #else
36 #include <unistd.h>
37 #endif
38 
39 #ifdef ENABLE_RECURSIVE
40 #include "recurse.h"
41 #endif
42 
43 /*Gcc uses LL as a suffix for long long int (64 bit) types - Marc Brooker 8/4/2004*/
44 #ifdef __GNUC__
45 #define ROUND64(x)   ( doubletmp = (x) + Dither.Add + (Int64_t)0x001FFFFD80000000LL, *(Int64_t*)(&doubletmp) - (Int64_t)0x433FFFFD80000000LL )
46 #else
47 #define ROUND64(x)   ( doubletmp = (x) + Dither.Add + (Int64_t)0x001FFFFD80000000L, *(Int64_t*)(&doubletmp) - (Int64_t)0x433FFFFD80000000L )
48 #endif
49 
50 #include <errno.h>
51 static int xrename(const char *oldpath, const char *newpath);
52 
53 extern int          write_to_log;
54 dither_t            Dither;
55 double              doubletmp;
56 double              total_samples;
57 double              total_files;
58 
59 /* Replaced with a double based function for consistency 2005-11-17
60 static float FABS(float x)
61 {
62 	unsigned int *ix=(unsigned int *)&x;
63 	*ix&=0x7fffffffUL;
64 	return(x);
65 }
66 */
DABS(double x)67 static double DABS(double x)
68 {
69 	Uint64_t *ix=(Uint64_t *)&x;
70 #ifdef __GNUC__
71 	*ix&=0x7fffffffffffffffULL;
72 #else
73 	*ix&=0x7fffffffffffffff;
74 #endif
75 	return(x);
76 }
77 
78 /* Dither output */
dither_output(int dithering,int shapingtype,int i,double Sum,int k,int format)79 Int64_t dither_output(int dithering, int shapingtype, int i, double Sum, int k, int format)
80 {
81 	double Sum2;
82 	Int64_t val;
83 	if(dithering) {
84 		if(!shapingtype) {
85 			double  tmp = Random_Equi ( Dither.Dither );
86 			Sum2 = tmp - Dither.LastRandomNumber [k];
87 			Dither.LastRandomNumber [k] = (int)tmp;
88 			Sum2 = Sum += Sum2;
89 			val = ROUND64 (Sum2)  &  Dither.Mask;
90 		}
91 		else {
92 			Sum2  = Random_Triangular ( Dither.Dither ) - scalar16 ( Dither.DitherHistory[k], Dither.FilterCoeff + i );
93 			Sum  += Dither.DitherHistory [k] [(-1-i)&15] = (float)Sum2;
94 			Sum2  = Sum + scalar16 ( Dither.ErrorHistory [k], Dither.FilterCoeff + i );
95 			val = ROUND64 (Sum2)  &  Dither.Mask;
96 			Dither.ErrorHistory [k] [(-1-i)&15] = (float)(Sum - val);
97 		}
98 	}
99 	else
100 		val = (Int64_t)(ROUND64 (Sum));
101 
102 	if (format == WAV_FMT_8BIT)
103 		val = val >> 24;
104 	else if (format == WAV_FMT_16BIT || format == WAV_FMT_AIFF)
105 		val = val >> 16;
106 	else if (format == WAV_FMT_24BIT)
107 		val = val >> 8;
108 
109 	return (val);
110 }
111 
112 /* Get the gain and peak value for a file. Runs in audiophile mode if
113  * audiophile is true.
114  *
115  * If an error occured, 0 is returned (a message has been printed).
116  */
117 
get_gain(const char * filename,double * track_peak,double * track_gain,double * dc_offset,double * offset,SETTINGS * settings)118 int get_gain(const char *filename, double *track_peak, double *track_gain,
119              double *dc_offset, double *offset, SETTINGS *settings)
120 {
121 	wavegain_opt *wg_opts = malloc(sizeof(wavegain_opt));
122 	FILE         *infile;
123 	int          result = 0;
124 	double       new_peak,
125 	             factor_clip,
126 	             scale,
127 	             peak = 0.,
128 	             dB;
129 	int          k, i;
130 	long         chunk;
131 	input_format *format;
132 
133 	memset(wg_opts, 0, sizeof(wavegain_opt));
134 
135 	wg_opts->force = settings->force;
136 
137 	if(!strcmp(filename, "-")) {
138 		infile = stdin;
139 		settings->apply_gain = 0;
140 		wg_opts->std_in = 1;
141 #ifdef _WIN32
142 		_setmode( _fileno(stdin), _O_BINARY );
143 #endif
144 	}
145 	else
146 		infile = fopen(filename, "rb");
147 
148 	if (infile == NULL) {
149 		fprintf (stderr, " Not able to open input file %s.\n", filename) ;
150 		goto exit;
151 	}
152 	wg_opts->apply_gain = 0;
153 
154 	/*
155 	 * Now, we need to select an input audio format
156 	 */
157 
158 	format = open_audio_file(infile, wg_opts);
159 	if (!format) {
160 		/* error reported by reader */
161 		fprintf (stderr, " Unrecognized file format for %s.\n", filename);
162 		goto exit;
163 	}
164 
165 	if (wg_opts->gain_chunk == 1 && !wg_opts->force) {
166 		fprintf (stderr, " Skipping File %s, it has already been processed.\n", filename);
167 //		result = 1;
168 		goto exit;
169 	}
170 
171 	if ((wg_opts->channels != 1) && (wg_opts->channels != 2)) {
172 		fprintf(stderr, " Unsupported number of channels.\n");
173 		goto exit;
174 	}
175 
176 	/* Only initialize gain analysis once in audiophile mode */
177 	if (settings->first_file || !settings->audiophile) {
178 		if (InitGainAnalysis(wg_opts->rate) != INIT_GAIN_ANALYSIS_OK) {
179 			fprintf(stderr, " Error Initializing Gain Analysis (nonstandard samplerate?)\n");
180 			goto exit;
181 		}
182 	}
183 
184 	if (settings->first_file) {
185 		total_samples = (double)wg_opts->total_samples_per_channel;
186 		fprintf(stderr, "\n Analyzing...\n\n");
187 		fprintf(stderr, "    Gain   |  Peak  | Scale | New Peak |Left DC|Right DC| Track\n");
188 		fprintf(stderr, "           |        |       |          |Offset | Offset |\n");
189 		fprintf(stderr, " --------------------------------------------------------------\n");
190 		if(write_to_log) {
191 			write_log("\n Analyzing...\n\n");
192 			write_log("    Gain   |  Peak  | Scale | New Peak |Left DC|Right DC| Track\n");
193 			write_log("           |        |       |          |Offset | Offset |\n");
194 			write_log(" --------------------------------------------------------------\n");
195 		}
196 		settings->first_file = 0;
197 	}
198 	else
199 		total_samples += (double)wg_opts->total_samples_per_channel;
200 
201 	if (settings->fast && (wg_opts->total_samples_per_channel * (wg_opts->samplesize / 8)
202 			* wg_opts->channels > 8192000)) {
203 
204 		long samples_read;
205 		double **buffer = malloc(sizeof(double *) * wg_opts->channels);
206 
207 		for (i = 0; i < wg_opts->channels; i++)
208 			buffer[i] = malloc(BUFFER_LEN * sizeof(double));
209 
210 		chunk = ((wg_opts->total_samples_per_channel * (wg_opts->samplesize / 8) * wg_opts->channels) + 44) / 1200;
211 
212 		for(k = 100; k < 1100; k+=5) {
213 
214 			samples_read = wg_opts->read_samples(wg_opts->readdata, buffer, BUFFER_LEN,
215 							    settings->fast, chunk * k);
216 			if (samples_read == 0) {
217 				break;
218 			}
219 			else {
220 				if (samples_read < 0) {
221 					/* Error in the stream. Not a problem, just reporting it in case
222 					 * we (the app) cares. In this case, we don't
223 					 */
224 				}
225 				else {
226 					int i;
227 					int j;
228 
229 					for (i = 0; i < wg_opts->channels; i++) {
230 						for (j = 0; j < samples_read; j++) {
231 							buffer[i][j] *= 0x7fff;
232 							if (DABS(buffer[i][j]) > peak)
233 								peak = DABS(buffer[i][j]);
234 						}
235 					}
236 
237 					if (AnalyzeSamples(buffer[0], buffer[1], samples_read,
238 							   wg_opts->channels) != GAIN_ANALYSIS_OK) {
239 						fprintf(stderr, " Error processing samples.\n");
240 						for (i = 0; i < wg_opts->channels; i++)
241 							if (buffer[i]) free(buffer[i]);
242 						if (buffer) free(buffer);
243 						goto exit;
244 					}
245 				}
246 			}
247 		}
248 		for (i = 0; i < wg_opts->channels; i++)
249 			if (buffer[i]) free(buffer[i]);
250 		if (buffer) free(buffer);
251 	}
252 	else
253 	{
254 		long samples_read;
255 		double **buffer = malloc(sizeof(double *) * wg_opts->channels);
256 
257 		for (i = 0; i < wg_opts->channels; i++)
258 			buffer[i] = malloc(BUFFER_LEN * sizeof(double));
259 
260 		while (1) {
261 
262 			samples_read = wg_opts->read_samples(wg_opts->readdata, buffer, BUFFER_LEN, 0, 0);
263 
264 			if (samples_read == 0) {
265 				break;
266 			}
267 			else {
268 				if (samples_read < 0) {
269 					/* Error in the stream. Not a problem, just reporting it in case
270 					 * we (the app) cares. In this case, we don't
271 					 */
272 				}
273 				else {
274 					int i;
275 					int j;
276 
277 					for (i = 0; i < wg_opts->channels; i++) {
278 						for (j = 0; j < samples_read; j++) {
279 							offset[i] += buffer[i][j];
280 							buffer[i][j] *= 0x7fff;
281 							if (DABS(buffer[i][j]) > peak)
282 								peak = DABS(buffer[i][j]);
283 						}
284 					}
285 
286 					if (AnalyzeSamples(buffer[0], buffer[1], samples_read,
287 							   wg_opts->channels) != GAIN_ANALYSIS_OK) {
288 						fprintf(stderr, " Error processing samples.\n");
289 						for (i = 0; i < wg_opts->channels; i++)
290 							if (buffer[i]) free(buffer[i]);
291 						if (buffer) free(buffer);
292 						goto exit;
293 					}
294 				}
295 			}
296 		}
297 
298 		for (i = 0; i < wg_opts->channels; i++) {
299 			if (buffer[i]) free(buffer[i]);
300 			dc_offset[i] = (double)(offset[i] / wg_opts->total_samples_per_channel);
301 		}
302 		if (buffer) free(buffer);
303 	}
304 	/*
305 	 * calculate factors for ReplayGain and ClippingPrevention
306 	 */
307 	*track_gain = (GetTitleGain() + settings->man_gain);
308 	scale = (pow(10., *track_gain * 0.05));
309 	if(settings->clip_prev) {
310 		factor_clip  = (32767./( peak + 1));
311 		if(scale < factor_clip)
312 			factor_clip = 1.0;
313 		else
314 			factor_clip /= scale;
315 		scale *= factor_clip;
316 	}
317 	new_peak = (peak * scale);
318 
319         dB = 20. * log10(scale);
320 	*track_gain = dB;
321 	{
322 		int dc_l;
323 		int dc_r;
324 		if (settings->no_offset) {
325 			dc_l = 0;
326 			dc_r = 0;
327 		}
328 		else {
329 			dc_l = (int)(dc_offset[0] * 32768 * -1);
330 			dc_r = (int)(dc_offset[1] * 32768 * -1);
331 		}
332 		fprintf(stderr, " %+6.2lf dB | %6.0lf | %5.2lf | %8.0lf | %4d  |  %4d  | %s\n",
333 			*track_gain, peak, scale, new_peak, dc_l, dc_r, filename);
334 		if(write_to_log) {
335 			write_log(" %+6.2lf dB | %6.0lf | %5.2lf | %8.0lf | %4d  |  %4d  | %s\n",
336 				*track_gain, peak, scale, new_peak, dc_l, dc_r, filename);
337 		}
338 	}
339 	if (settings->scale && !settings->audiophile)
340 		fprintf(stdout, "%8.6lf", scale);
341 
342 	settings->album_peak = settings->album_peak < peak ? peak : settings->album_peak;
343 	*track_peak = new_peak;
344 	result = 1;
345 
346 exit:
347 	if (result)
348 		format->close_func(wg_opts->readdata);
349 	if (wg_opts)
350 		free(wg_opts);
351 	if (infile)
352 		fclose(infile);
353 	return result;
354 }
355 
356 
357 /* Use the ReplayGain calculations to adjust the gain on the wave file.
358  * If audiophile_gain is selected, that value is used, otherwise the
359  * radio_gain value is used.
360  */
write_gains(const char * filename,double radio_gain,double audiophile_gain,double TitlePeak,double * dc_offset,double * album_dc_offset,SETTINGS * settings)361 int write_gains(const char *filename, double radio_gain, double audiophile_gain, double TitlePeak,
362                 double *dc_offset, double *album_dc_offset, SETTINGS *settings)
363 {
364 	wavegain_opt *wg_opts = malloc(sizeof(wavegain_opt));
365 	FILE         *infile;
366 	audio_file   *aufile;
367 	int          readcount,
368 	             result = 0,
369 	             delete_temp = 0,
370 	             i;
371 	double       Gain;
372 	double       scale;
373 	double       total_read = 0.;
374 	double       wrap_prev_pos;
375 	double       wrap_prev_neg;
376 	void         *sample_buffer;
377 	input_format *format;
378 
379 	memset(wg_opts, 0, sizeof(wavegain_opt));
380 
381 	wg_opts->force = settings->force;
382 	wg_opts->undo = settings->undo;
383 
384 	infile = fopen(filename, "rb");
385 
386 	if (infile == NULL) {
387 		fprintf (stderr, " Not able to open input file %s.\n", filename) ;
388 		goto exit;
389 	}
390 	wg_opts->apply_gain = 1;
391 	wg_opts->write_chunk = settings->write_chunk;
392 
393 	/*
394 	 * Now, we need to select an input audio format
395 	 */
396 
397 	format = open_audio_file(infile, wg_opts);
398 	if (!format) {
399 		format->close_func(wg_opts->readdata);
400 		if (wg_opts)
401 			free(wg_opts);
402 		fclose(infile);
403 		/* error reported by reader */
404 		fprintf (stderr, " Unrecognized file format for %s.\n", filename) ;
405 	}
406 	else if (wg_opts->undo && !wg_opts->gain_chunk) {
407 		format->close_func(wg_opts->readdata);
408 		if (wg_opts)
409 			free(wg_opts);
410 		fclose(infile);
411 		fprintf(stderr, " Skipping file: %s - 'gain' chunk not found.\n", filename);
412 		if (write_to_log) {
413 			write_log(" Skipping file: %s - 'gain' chunk not found.\n", filename);
414 		}
415 		result = 1;
416 	}
417 	else if (wg_opts->gain_scale == 1.0 && !wg_opts->force) {
418 		format->close_func(wg_opts->readdata);
419 		if (wg_opts)
420 			free(wg_opts);
421 		fclose(infile);
422 		fprintf(stderr, " Skipping file: %s - Gain already undone.\n", filename);
423 		if (write_to_log) {
424 			write_log(" Skipping file: %s - Gain already undone.\n", filename);
425 		}
426 		result = 1;
427 	}
428 	else {
429 		double **pcm = malloc(sizeof(double *) * wg_opts->channels);
430 
431 		for (i = 0; i < wg_opts->channels; i++)
432 			pcm[i] = malloc(BUFFER_LEN * sizeof(double));
433 
434 		switch(settings->format) {
435 			case WAV_NO_FMT:
436 				if (wg_opts->format == WAV_FMT_AIFF || wg_opts->format == WAV_FMT_AIFC8
437 								   || wg_opts->format == WAV_FMT_AIFC16) {
438 					wg_opts->format = WAV_FMT_AIFF;
439 					wrap_prev_pos = 0x7fff;
440 					wrap_prev_neg = -0x8000;
441 				}
442 				else if (wg_opts->format == WAV_FMT_8BIT) {
443 					wrap_prev_pos = 0x7f;
444 					wrap_prev_neg = -0x80;
445 				}
446 				else if (wg_opts->format == WAV_FMT_16BIT) {
447 					wrap_prev_pos = 0x7fff;
448 					wrap_prev_neg = -0x8000;
449 				}
450 				else if (wg_opts->format == WAV_FMT_24BIT) {
451 					wrap_prev_pos = 0x7fffff;
452 					wrap_prev_neg = -0x800000;
453 				}
454 				else if (wg_opts->format == WAV_FMT_32BIT) {
455 					wrap_prev_pos = 0x7fffffff;
456 					wrap_prev_neg = -0x7fffffff;
457 				}
458 				else if (wg_opts->format == WAV_FMT_FLOAT) {
459 					wrap_prev_pos = 1 - (1 / 0x80000000);
460 					wrap_prev_neg = -1.;
461 				}
462 				break;
463 			case WAV_FMT_8BIT:
464 				wg_opts->format = WAV_FMT_8BIT;
465 				wg_opts->samplesize = 8;
466 				wrap_prev_pos = 0x7f;
467 				wrap_prev_neg = -0x80;
468 				break;
469 			case WAV_FMT_AIFF:
470 				wg_opts->format = WAV_FMT_AIFF;
471 				wg_opts->samplesize = 16;
472 				wg_opts->endianness = BIG;
473 				wrap_prev_pos = 0x7fff;
474 				wrap_prev_neg = -0x8000;
475 				break;
476 			case WAV_FMT_16BIT:
477 				wg_opts->format = WAV_FMT_16BIT;
478 				wg_opts->samplesize = 16;
479 				wg_opts->endianness = LITTLE;
480 				wrap_prev_pos = 0x7fff;
481 				wrap_prev_neg = -0x8000;
482 				break;
483 			case WAV_FMT_24BIT:
484 				wg_opts->format = WAV_FMT_24BIT;
485 				wg_opts->samplesize = 24;
486 				wg_opts->endianness = LITTLE;
487 				wrap_prev_pos = 0x7fffff;
488 				wrap_prev_neg = -0x800000;
489 				break;
490 			case WAV_FMT_32BIT:
491 				wg_opts->format = WAV_FMT_32BIT;
492 				wg_opts->samplesize = 32;
493 				wg_opts->endianness = LITTLE;
494 				wrap_prev_pos = 0x7fffffff;
495 				wrap_prev_neg = -0x7fffffff;
496 				break;
497 			case WAV_FMT_FLOAT:
498 				wg_opts->format = WAV_FMT_FLOAT;
499 				wg_opts->samplesize = 32;
500 				wg_opts->endianness = LITTLE;
501 				wrap_prev_pos = 1 - (1 / 0x80000000);
502 				wrap_prev_neg = -1.;
503 				break;
504 		}
505 
506 		wg_opts->std_out = settings->std_out;
507 
508 		aufile = open_output_audio_file(TEMP_NAME, wg_opts);
509 
510 		if (aufile == NULL) {
511 			fprintf (stderr, " Not able to open output file %s.\n", TEMP_NAME);
512 			fclose(infile);
513 			goto exit;
514 		}
515 
516 		Init_Dither (wg_opts->samplesize, settings->shapingtype);
517 
518 		if (wg_opts->undo) {
519 			scale = 1.0 / wg_opts->gain_scale;
520 		        Gain = 20. * log10(scale);
521 			wg_opts->gain_scale = 1.0;
522 		}
523 		else {
524 			if (settings->audiophile)
525 				Gain = audiophile_gain;
526 			else
527 				Gain = radio_gain;
528 
529 			scale = pow(10., Gain * 0.05);
530 			wg_opts->gain_scale = scale;
531 		}
532 
533 		fprintf(stderr, "                                             \r");
534 		fprintf(stderr, " Applying Gain of %+5.2lf dB to file: %s\n", Gain, filename);
535 		if (write_to_log) {
536 			write_log(" Applying Gain of %+5.2lf dB to file: %s\n", Gain, filename);
537 		}
538 
539 		while (1) {
540 
541 			readcount = wg_opts->read_samples(wg_opts->readdata, pcm, BUFFER_LEN, 0, 0);
542 
543 			total_read += ((double)readcount / wg_opts->rate);
544 			total_files += ((double)readcount / wg_opts->rate);
545 			if( (long)total_files % 4 == 0) {
546 				if (wg_opts->undo)
547 					fprintf(stderr, "This file %3.0lf%% done\r",
548 						total_read / (wg_opts->total_samples_per_channel / wg_opts->rate) * 100);
549 				else
550 					fprintf(stderr, "This file %3.0lf%% done\tAll files %3.0lf%% done\r",
551 						total_read / (wg_opts->total_samples_per_channel / wg_opts->rate) * 100,
552 						total_files / (total_samples / wg_opts->rate) * 100);
553 			}
554 
555 			if (readcount == 0) {
556 				break;
557 			}
558 			else if (readcount < 0) {
559 				/* Error in the stream. Not a problem, just reporting it in case
560 				 * we (the app) cares. In this case, we don't
561 				 */
562 			}
563 			else {
564 				int   convsize = BUFFER_LEN;
565 				int   j,
566 				      i = 0,
567 				      k;
568 				int   bout = (readcount < convsize ? readcount : convsize);
569 
570 				/* scale doubles to 8, 16, 24 or 32 bit signed ints
571 				 * (host order) (unless float output)
572 				 * and apply ReplayGain scaling, etc.
573 				 */
574 				sample_buffer = malloc(sizeof(double) * wg_opts->channels * bout);
575 				for(k = 0; k < wg_opts->channels; k++) {
576 					for(j = 0; j < bout; j++, i++) {
577 						Int64_t val;
578 						double Sum;
579 
580 						if (!settings->no_offset) {
581 							if (settings->adc)
582 								pcm[k][j] -= album_dc_offset[k];
583 							else
584 								pcm[k][j] -= dc_offset[k];
585 						}
586 
587 						pcm[k][j] *= scale;
588 						if (settings->limiter) {	/* hard 6dB limiting */
589 							if (pcm[k][j] < -0.5)
590 								pcm[k][j] = tanh((pcm[k][j] + 0.5) / (1-0.5)) * (1-0.5) - 0.5;
591 							else if (pcm[k][j] > 0.5)
592 								pcm[k][j] = tanh((pcm[k][j] - 0.5) / (1-0.5)) * (1-0.5) + 0.5;
593 						}
594 						if (wg_opts->format != WAV_FMT_FLOAT) {
595 							Sum = pcm[k][j]*2147483647.f;
596 							if (i > 31)
597 								i = 0;
598 							val = dither_output(settings->dithering, settings->shapingtype, i,
599 									    Sum, k, wg_opts->format);
600 							if (val > (Int64_t)wrap_prev_pos)
601 								val = (Int64_t)wrap_prev_pos;
602 							else if (val < (Int64_t)wrap_prev_neg)
603 								val = (Int64_t)wrap_prev_neg;
604 							pcm[k][j] = (double)val;
605 						}
606 						else {
607 							if (pcm[k][j] > wrap_prev_pos)
608 								pcm[k][j] = wrap_prev_pos;
609 							else if (pcm[k][j] < wrap_prev_neg)
610 								pcm[k][j] = wrap_prev_neg;
611 						}
612 					}
613 				}
614 				sample_buffer = output_to_PCM(pcm, sample_buffer, wg_opts->channels,
615 						bout, wg_opts->format);
616 				/* write to file */
617 				write_audio_file(aufile, sample_buffer, bout * wg_opts->channels);
618 
619 				free(sample_buffer);
620 			}
621 		}
622 		for (i = 0; i < wg_opts->channels; i++)
623 			if (pcm[i]) free(pcm[i]);
624 		if (pcm) free(pcm);
625 		format->close_func(wg_opts->readdata);
626 		close_audio_file(infile, aufile, wg_opts);
627 		fclose(infile);
628 
629 		if (!settings->std_out) {
630 			if (remove(filename) != 0) {
631 				fprintf(stderr, " Error deleting old file '%s'\n", filename);
632 				goto exit;
633 			}
634 
635                        /*
636                        * int rename(const char *old, const char *new);
637                        * In POSIX, rename will fail if the 'old' and 'new' names are on different mounted file systems.
638                        * ( From http://en.wikipedia.org/wiki/Rename_%28C%29 )
639                        * Function 'xrename' from 'normalize-0.7.6' is one clever solution
640                        */
641                        /*if (rename(TEMP_NAME, filename) != 0) {*/
642                         if (xrename(TEMP_NAME, filename) != 0) {
643 				fprintf(stderr, " Error renaming '" TEMP_NAME "' to '%s' (uh-oh)\n", filename);
644 				goto exit;
645 			}
646 		}
647 		result = 1;
648 	}
649 exit:
650 	return result;
651 }
652 
653 /* From normalize-0.7.6/nid3lib/write.c
654  * Move the file "oldpath" to "newpath", or copy and delete if they
655  * are on different filesystems.
656 */
657 static int
xrename(const char * oldpath,const char * newpath)658 xrename(const char *oldpath, const char *newpath)
659 {
660   FILE *in, *out;
661   char buf[4096];
662   size_t sz;
663 
664   if (strcmp(oldpath, newpath) == 0)
665     return 0;
666 
667 #ifdef __EMX__
668   if (unlink(newpath) == -1 && errno != ENOENT)
669     return -1;
670 #endif
671 
672   if (rename(oldpath, newpath) == -1) {
673     if (errno == EXDEV) {
674       /* files are on different filesystems, so we have to copy */
675       if (unlink(newpath) == -1 && errno != ENOENT)
676        return -1;
677 
678       in = fopen(oldpath, "rb");
679       if (in == NULL)
680        return -1;
681       out = fopen(newpath, "wb");
682       if (out == NULL) {
683        fclose(in);
684        return -1;
685       }
686 
687       while ((sz = fread(buf, 1, 4096, in)) > 0)
688        fwrite(buf, 1, sz, out);
689 
690       if (ferror(in) || ferror(out)) {
691        fclose(in);
692        fclose(out);
693        return -1;
694       }
695       if (fclose(in) == EOF) {
696        fclose(out);
697        return -1;
698       }
699       if (fclose(out) == EOF)
700        return -1;
701 
702       if (unlink(oldpath) == -1)
703        return -1;
704     } else {
705       return -1;
706     }
707   }
708 
709   return 0;
710 }
711