1 /*
2 * Handles all Vorbis-specific ReplayGain processing.
3 *
4 * This program is distributed under the GNU General Public License, version
5 * 2.1. A copy of this license is included with this source.
6 *
7 * Copyright (C) 2002, 2004 Gian-Carlo Pascutto and Magnus Holmgren
8 */
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include <math.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <time.h>
18 #include <vorbis/codec.h>
19 #include <vorbis/vorbisfile.h>
20 #include "gain_analysis.h"
21 #include "i18n.h"
22 #include "misc.h"
23 #include "vcedit.h"
24 #include "vorbis.h"
25
26 #include <sys/stat.h>
27 #ifdef WIN32
28 #include <io.h>
29 #include <sys/utime.h>
30 #else /* #ifdef WIN32 */
31 #include <utime.h>
32 #endif /* #ifdef WIN32 */
33
34
35 #ifndef MIN
36 #define MIN(x,y) ((x) < (y) ? (x) : (y))
37 #endif
38
39 #ifndef MAX
40 #define MAX(x,y) ((x) > (y) ? (x) : (y))
41 #endif
42
43 #define TAG_TRACK_GAIN "REPLAYGAIN_TRACK_GAIN"
44 #define TAG_TRACK_PEAK "REPLAYGAIN_TRACK_PEAK"
45 #define TAG_ALBUM_GAIN "REPLAYGAIN_ALBUM_GAIN"
46 #define TAG_ALBUM_PEAK "REPLAYGAIN_ALBUM_PEAK"
47 #define TAG_TRACK_GAIN_OLD "RG_RADIO"
48 #define TAG_TRACK_PEAK_OLD "RG_PEAK"
49 #define TAG_ALBUM_GAIN_OLD "RG_AUDIOPHILE"
50
51 #define VALUE_BUFFER_SIZE 64
52 #define PEAK_FORMAT "%1.8f"
53 #define GAIN_FORMAT "%+2.2f dB"
54 #define PROGRESS_FORMAT " %3d%% - %s\r"
55 /* Size of PROGRESS_FORMAT when printed, excluding the filename */
56 #define PROGRESS_FORMAT_SIZE 8
57 #define MIN_FILENAME_SIZE 5
58 #define MIN_MIDDLE_TRUNCATE_SIZE 20
59 #define TEMP_NAME "vorbisgain.tmpXXXXXX"
60
61
62 /**
63 * \brief See if a vorbis file contains ReplayGain tags.
64 *
65 * See if a vorbis file contain replay gain tags. A file is considered to
66 * have the tags if all required tags (track gain and peak, optionally album
67 * gain and peak) are present.
68 *
69 * \param filename name of the file to check.
70 * \param album_tags if true, check for the album tags (gain and peek) as
71 * well.
72 * \param any_tags if true, the presence of any tag will be enough for a
73 * file to contain tags. album_tags is ignored if any_tags
74 * is true.
75 * \param settings global settings and data.
76 * \return 1 if the file contains all the tags, 0 if the file didn't contain
77 * the tags and -1 if an error occured (in which case a message has
78 * been printed).
79 */
has_tags(const char * filename,int album_tags,int any_tags,SETTINGS * settings)80 int has_tags(const char* filename, int album_tags, int any_tags,
81 SETTINGS* settings)
82 {
83 vcedit_state* state = NULL;
84 vorbis_comment* vc;
85 FILE* file = NULL;
86 int found = 0;
87 int expected;
88 int result = -1;
89
90 if (any_tags)
91 {
92 expected = 0;
93 }
94 else if (album_tags)
95 {
96 expected = 4;
97 }
98 else
99 {
100 expected = 2;
101 }
102
103 state = vcedit_new_state();
104
105 if (state == NULL)
106 {
107 fprintf(stderr, _("Out of memory\n"));
108 return result;
109 }
110
111 file = fopen(filename, "rb");
112
113 if (file != NULL)
114 {
115 if (vcedit_open(state, file) >= 0)
116 {
117 int i;
118
119 vc = vcedit_comments(state);
120
121 for (i = 0; i < vc->comments; i++)
122 {
123 if(!tag_compare(vc->user_comments[i], TAG_TRACK_GAIN)
124 || !tag_compare(vc->user_comments[i], TAG_TRACK_PEAK))
125 {
126 found++;
127 }
128
129 if((album_tags || any_tags) &&
130 (!tag_compare(vc->user_comments[i], TAG_ALBUM_GAIN)
131 || !tag_compare(vc->user_comments[i], TAG_ALBUM_PEAK)))
132 {
133 found++;
134 }
135 }
136
137 result = (found >= expected) ? 1 : 0;
138 }
139 else
140 {
141 if (!settings->skip)
142 {
143 fprintf(stderr, _("Couldn't open file '%s' as vorbis: %s\n"),
144 filename, vcedit_error(state));
145 }
146 }
147
148 fclose(file);
149 }
150 else
151 {
152 file_error(_("Couldn't open file '%s': "), filename);
153 }
154
155 vcedit_clear(state);
156 return result;
157 }
158
159
160 /**
161 * \brief Get old style tag values.
162 *
163 * Get the old style tag values from a file.
164 *
165 * \param filename name of the file to check.
166 * \param album_tag if true, require file to contain album gain tag. If
167 * false, album gain is still returned if present.
168 * \param track_gain track gain is stored here.
169 * \param track_peak track peak is stored here.
170 * \param album_gain album gain is stored here.
171 * \param settings global settings and data.
172 * \return 1 if the file contains all the tags and their values coould be
173 * parsed, 0 if the file didn't contain all the tags and -1 if an
174 * error occured (in which case a message has been printed).
175 */
get_old_tags(const char * filename,int album_tag,float * track_peak,float * track_gain,float * album_gain,SETTINGS * settings)176 int get_old_tags(const char* filename, int album_tag, float* track_peak,
177 float* track_gain, float* album_gain, SETTINGS* settings)
178 {
179 vcedit_state* state = NULL;
180 vorbis_comment* vc;
181 FILE* file = NULL;
182 int found = 0;
183 int expected = album_tag ? 3 : 2;
184 int result = -1;
185
186 state = vcedit_new_state();
187
188 if (state == NULL)
189 {
190 fprintf(stderr, _("Out of memory\n"));
191 return result;
192 }
193
194 file = fopen(filename, "rb");
195
196 if (file != NULL)
197 {
198 if (vcedit_open(state, file) >= 0)
199 {
200 int i;
201
202 vc = vcedit_comments(state);
203
204 for (i = 0; i < vc->comments; i++)
205 {
206 if(!tag_compare(vc->user_comments[i], TAG_TRACK_GAIN_OLD))
207 {
208 if (sscanf(&vc->user_comments[i][sizeof(TAG_TRACK_GAIN_OLD)],
209 "%f", track_gain) == 1)
210 {
211 found++;
212 }
213 }
214
215 if (!tag_compare(vc->user_comments[i], TAG_TRACK_PEAK_OLD))
216 {
217 if (sscanf(&vc->user_comments[i][sizeof(TAG_TRACK_PEAK_OLD)],
218 "%f", track_peak) == 1)
219 {
220 found++;
221 }
222 }
223
224 if (!tag_compare(vc->user_comments[i], TAG_ALBUM_GAIN_OLD))
225 {
226 if (sscanf(&vc->user_comments[i][sizeof(TAG_ALBUM_GAIN_OLD)],
227 "%f", album_gain) == 1)
228 {
229 found++;
230 }
231 }
232 }
233
234 result = (found >= expected) ? 1 : 0;
235 }
236 else
237 {
238 if (!settings->skip)
239 {
240 fprintf(stderr, _("Couldn't open file '%s' as vorbis: %s\n"),
241 filename, vcedit_error(state));
242 }
243 }
244
245 fclose(file);
246 }
247 else
248 {
249 file_error(_("Couldn't open file '%s': "), filename);
250 }
251
252 vcedit_clear(state);
253 return result;
254 }
255
256 /**
257 * \brief Show/update a simple progress bar for a file.
258 *
259 * Show/update a simple progress bar for a file. The name will be truncated
260 * to fit within the current console's with, if possible.
261 *
262 * \param vf Vorbis file being processed.
263 * \param filename name of the file being processed.
264 * \param position position in the file, in percent (0-100).
265 */
show_progress(const char * filename,unsigned int position)266 void show_progress(const char* filename, unsigned int position)
267 {
268 unsigned int width;
269 unsigned int height;
270 char* short_name = NULL;
271 const char* name = filename;
272
273 if (!get_console_size(0, &width, &height))
274 {
275 unsigned int full_length = strlen(filename);
276
277 if (full_length + PROGRESS_FORMAT_SIZE > width)
278 {
279 /* Need room for cursor as well. */
280 int short_length = width - PROGRESS_FORMAT_SIZE - 1;
281
282 /* Not useful to cut it too short. */
283 short_length = MAX(short_length, MIN_FILENAME_SIZE + 3);
284 short_name = malloc(short_length + 1);
285 /* Need space for "..." */
286 short_length -= 3;
287
288 /* Put "..." in the middle if we have "enough" chars available. */
289 if (short_length > MIN_MIDDLE_TRUNCATE_SIZE)
290 {
291 strncpy(short_name, filename, short_length / 2);
292 short_name[short_length / 2] = '\0';
293 strcat(short_name, "...");
294 /* Round upwards here, so that all chars are used. */
295 strcat(short_name, &filename[full_length
296 - ((short_length + 1) / 2)]);
297 }
298 else
299 {
300 strcpy(short_name, "...");
301 strcat(short_name, &filename[full_length - short_length]);
302 }
303
304 name = short_name;
305 }
306 }
307
308 fprintf(stderr, _(PROGRESS_FORMAT), MIN(position, 100), name);
309
310 if (NULL != short_name)
311 {
312 free(short_name);
313 }
314 }
315
316 /**
317 * Get the replaygain and peak value for a file.
318 *
319 * \param filename name of the file to get the gain and peak for.
320 * \param track_peak where to store track peak value.
321 * \param track_gain where to store track gain value.
322 * \param settings global settings and data.
323 * \return 0 if successful and -1 if an error occurred (in which case a
324 * message has been printed).
325 */
get_gain(const char * filename,float * track_peak,float * track_gain,SETTINGS * settings)326 int get_gain(const char* filename, float* track_peak, float* track_gain,
327 SETTINGS* settings)
328 {
329 OggVorbis_File vf;
330 FILE* file = NULL;
331 vorbis_info* vi = NULL;
332 ogg_int64_t file_length = -1;
333 float peak = 0.f;
334 float scale;
335 float new_peak;
336 long rate;
337 int previous_percentage = -1;
338 time_t previous_time = 0;
339 int previous_section = 0;
340 int current_section;
341 int channels;
342 int result;
343
344 file = fopen(filename, "rb");
345
346 if (file == NULL)
347 {
348 fprintf(stderr, _("Couldn't open file '%s'\n"), filename);
349 return -1;
350 }
351
352 result = ov_open(file, &vf, NULL, 0);
353
354 if(result < 0)
355 {
356 if (!settings->skip)
357 {
358 vorbis_error(result, _("Couldn't process '%s': "), filename);
359 }
360
361 return -1;
362 }
363
364 vi = ov_info(&vf, -1);
365
366 if ((vi->channels != 1) && (vi->channels != 2))
367 {
368 fprintf(stderr, _("Unsupported number of channels (%d) in '%s'.\n"),
369 vi->channels, filename);
370 ov_clear(&vf);
371 return -1;
372 }
373
374 /* Only initialize gain analysis once per album, when in album mode */
375 if (settings->first_file || !settings->album)
376 {
377 if (InitGainAnalysis(vi->rate) != INIT_GAIN_ANALYSIS_OK)
378 {
379 fprintf(stderr, _("Couldn't initialize gain analysis"
380 " (nonstandard samplerate?) for '%s'\n"), filename);
381 ov_clear(&vf);
382 return -1;
383 }
384 }
385
386 channels = vi->channels;
387 rate = vi->rate;
388
389 if (settings->first_file)
390 {
391 if (!settings->quiet)
392 {
393 fprintf(stdout, _("Analyzing files...\n\n"));
394 fprintf(stdout, _(" Gain | Peak | Scale | New Peak | Track\n"));
395 fprintf(stdout, _("----------+--------+-------+----------+------\n"));
396 }
397
398 settings->first_file = 0;
399 }
400
401 if (!settings->quiet)
402 {
403 file_length = ov_pcm_total(&vf, -1);
404
405 if (settings->show_progress && (file_length > 0)
406 && (ov_pcm_tell(&vf) >= 0))
407 {
408 show_progress(filename, 0);
409 }
410 }
411
412 while (1)
413 {
414 float** pcm;
415 float* left_pcm;
416 float* right_pcm = NULL;
417 long ret;
418
419 #ifdef VORBISFILE_OV_READ_FLOAT_3_ARGS
420 ret = ov_read_float(&vf, &pcm, ¤t_section);
421 #else
422 ret = ov_read_float(&vf, &pcm, 1024, ¤t_section);
423 #endif
424
425 left_pcm = pcm[0];
426
427 if (vi->channels == 2)
428 {
429 right_pcm = pcm[1];
430 }
431
432 if (ret == 0)
433 {
434 break;
435 }
436 else if (ret < 0)
437 {
438 /* Error in the stream. Not a problem, just reporting it in case
439 * we (the app) cares. In this case, we don't
440 */
441 }
442 else
443 {
444 long i;
445
446 /* We can't handle changes in sample rate or number of channels.
447 * Channels would probably be OK (at least 1 to 2 and vice versa),
448 * but... :)
449 */
450 if (current_section != previous_section)
451 {
452 previous_section = current_section;
453 vi = ov_info(&vf, -1);
454
455 if ((channels != vi->channels) || (rate != vi->rate))
456 {
457 fprintf(stderr, _("Can't process chained file '%s'\n"
458 "with changing stream properties (from %ld Hz, "
459 "%d channel(s)\nto %ld Hz, %d channel(s).\n"),
460 filename, rate, channels, vi->rate, vi->channels);
461 ov_clear(&vf);
462 return -1;
463 }
464 }
465
466 for (i = 0; i < ret; i++)
467 {
468 left_pcm[i] *= 32767.;
469
470 if (fabs(left_pcm[i]) > peak)
471 {
472 peak = (float) fabs(left_pcm[i]);
473 }
474
475 if (vi->channels == 2)
476 {
477 right_pcm[i] *= 32767.;
478
479 if (fabs(right_pcm[i]) > peak)
480 {
481 peak = (float) fabs(right_pcm[i]);
482 }
483 }
484 }
485
486 if (AnalyzeSamples(left_pcm, right_pcm, ret, vi->channels)
487 != GAIN_ANALYSIS_OK)
488 {
489 fprintf(stderr, _("Couldn't analyze samples in file '%s'\n"),
490 filename);
491 ov_clear(&vf);
492 return -1;
493 }
494 }
495
496 if (file_length > 0)
497 {
498 /* ov_time_tell is slow, so we use ov_pcm_tell instead */
499 ogg_int64_t file_pos = ov_pcm_tell(&vf);
500
501 if (file_pos > 0)
502 {
503 int percentage = (int) floor((((double) file_pos / file_length) * 100) + 0.5);
504 time_t curr_time;
505
506 time(&curr_time);
507
508 if ((100 == percentage) || ((percentage != previous_percentage)
509 && (difftime(curr_time, previous_time) >= 1)))
510 {
511 if (settings->show_progress)
512 {
513 show_progress(filename, percentage);
514 }
515 previous_percentage = percentage;
516 previous_time = curr_time;
517 }
518 }
519 }
520 }
521
522 *track_gain = GetTitleGain();
523 scale = (float) pow(10., *track_gain / 20.);
524 *track_peak = (float) (peak / 32767.);
525 new_peak = peak * scale;
526
527 if (!settings->quiet)
528 {
529 fprintf(stdout, _("%+6.2f dB | %6.0f | %5.2f | %8.0f | %s\n"),
530 *track_gain, peak, scale, new_peak, filename);
531 }
532
533 ov_clear(&vf);
534 return 0;
535 }
536
537
538 /**
539 * \brief Write the replay gain tags to a file.
540 *
541 * Write the replay gain tags to a file, removing any old style tags. Any
542 * other tags remain unchanged. If track_gain or album_gain contains NO_GAIN,
543 * no corresponding tag is written. If track_peak or album_peak contains
544 * NO_PEAK, no corresponding tag is written. However, if NO_GAIN or NO_PEAK is
545 * specified, any already present tag of the corresponding type will remain.
546 *
547 * \param filename name of the file to write the tags to.
548 * \param track_peak track peak value to write, or NO_PEAK.
549 * \param track_gain track replay gain value to write, or NO_GAIN.
550 * \param album_peak album peak value to write, or NO_PEAK.
551 * \param album_gain album gain value to write, or NO_GAIN.
552 * \param verbose print processing messages.
553 * \param remove_tags if true, remove all replay gain tags from the file
554 * (both old and new style). Any track or album values are
555 * ignored.
556 * \return 0 if successful and -1 if an error occurred (in which case a
557 * message has been printed).
558 */
write_gains(const char * filename,float track_peak,float track_gain,float album_peak,float album_gain,int verbose,int remove_tags)559 int write_gains(const char *filename, float track_peak, float track_gain,
560 float album_peak, float album_gain, int verbose, int remove_tags)
561 {
562 struct stat stat_buf;
563 struct utimbuf utime_buf;
564 vcedit_state* state = NULL;
565 vorbis_comment* vc;
566 FILE* infile = NULL;
567 FILE* outfile = NULL;
568 char** new_tags = NULL;
569 char* temp_name = NULL;
570 char buffer[VALUE_BUFFER_SIZE];
571 int new_count = 0;
572 int result = -1;
573 int delete_temp = 0;
574 int i;
575
576 infile = fopen(filename, "rb");
577
578 if (infile == NULL)
579 {
580 file_error(_("Couldn't open '%s' for input: "), filename);
581 goto exit;
582 }
583
584 state = vcedit_new_state();
585
586 if (vcedit_open(state, infile) < 0)
587 {
588 fprintf(stderr, _("Couldn't open file '%s' as vorbis: %s\n"),
589 filename, vcedit_error(state));
590 goto exit;
591 }
592
593 vc = vcedit_comments(state);
594 new_tags = (char**) calloc(vc->comments, sizeof(char*));
595
596 for (i = 0; i < vc->comments; i++)
597 {
598 /* Copy all tags, except those we wish to change.
599 * Alternatively, remove all ReplayGain tags.
600 */
601 int copy_tag = 1;
602
603 /* Remove any old style tags */
604
605 if (!tag_compare(vc->user_comments[i], TAG_TRACK_GAIN_OLD)
606 || !tag_compare(vc->user_comments[i], TAG_TRACK_PEAK_OLD)
607 || !tag_compare(vc->user_comments[i], TAG_ALBUM_GAIN_OLD))
608 {
609 copy_tag = 0;
610 }
611
612 /* Check for tags */
613
614 if (!tag_compare(vc->user_comments[i], TAG_TRACK_GAIN)
615 && (remove_tags || (track_gain > NO_GAIN)))
616 {
617 copy_tag = 0;
618 }
619
620 if (!tag_compare(vc->user_comments[i], TAG_TRACK_PEAK)
621 && (remove_tags || (track_peak > NO_PEAK)))
622 {
623 copy_tag = 0;
624 }
625
626 if (!tag_compare(vc->user_comments[i], TAG_ALBUM_GAIN)
627 && (remove_tags || (album_gain > NO_GAIN)))
628 {
629 copy_tag = 0;
630 }
631
632 if (!tag_compare(vc->user_comments[i], TAG_ALBUM_PEAK)
633 && (remove_tags || (album_peak > NO_PEAK)))
634 {
635 copy_tag = 0;
636 }
637
638 if (copy_tag)
639 {
640 new_tags[new_count] = strdup(vc->user_comments[i]);
641
642 if (new_tags[new_count++] == NULL)
643 {
644 fprintf(stderr, _("Out of memory\n"));
645 goto exit;
646 }
647 }
648 }
649
650 vorbis_comment_clear(vc);
651 vorbis_comment_init(vc);
652
653 /* Add the old tags to the new file */
654 for (i = 0; i < new_count; i++)
655 {
656 vorbis_comment_add(vc, new_tags[i]);
657 }
658
659 /* Add new tags - unless ReplayGain tags are to be removed */
660 if (!remove_tags)
661 {
662 if (track_peak > NO_PEAK)
663 {
664 sprintf(buffer, PEAK_FORMAT, track_peak);
665 vorbis_comment_add_tag(vc, TAG_TRACK_PEAK, buffer);
666 }
667
668 if (track_gain > NO_GAIN)
669 {
670 sprintf(buffer, GAIN_FORMAT, track_gain);
671 vorbis_comment_add_tag(vc, TAG_TRACK_GAIN, buffer);
672 }
673
674 if (album_peak > NO_PEAK)
675 {
676 sprintf(buffer, PEAK_FORMAT, album_peak);
677 vorbis_comment_add_tag(vc, TAG_ALBUM_PEAK, buffer);
678 }
679
680 if (album_gain > NO_GAIN)
681 {
682 sprintf(buffer, GAIN_FORMAT, album_gain);
683 vorbis_comment_add_tag(vc, TAG_ALBUM_GAIN, buffer);
684 }
685 }
686
687 /* Make sure temp is in same folder as file. And yes, the malloc is larger
688 * than necessary (and not always needed). Lets keep it simple though (at
689 * the expense of a few bytes)...
690 */
691 temp_name = malloc(strlen(filename) + sizeof(TEMP_NAME));
692
693 if (temp_name == NULL)
694 {
695 fprintf(stderr, _("Out of memory\n"));
696 goto exit;
697 }
698
699 strcpy(temp_name, filename);
700 strcpy((char *) last_path(temp_name), TEMP_NAME);
701
702 #ifdef WIN32
703 temp_name = _mktemp(temp_name);
704 #else
705 temp_name = mktemp(temp_name);
706 #endif
707
708 if (temp_name == NULL)
709 {
710 fprintf(stderr, _("Couldn't create temporary file for processing.\n"));
711 goto exit;
712 }
713
714 outfile = fopen(temp_name, "wb");
715
716 if (outfile == NULL)
717 {
718 file_error(_("Couldn't open '%s' for output: "), temp_name);
719 goto exit;
720 }
721
722 if (verbose)
723 {
724 fprintf(stderr, remove_tags
725 ? _("Removing tags from '%s'\n")
726 : _("Writing tags to '%s'\n") , filename);
727 }
728
729 if (vcedit_write(state, outfile) < 0)
730 {
731 /* Not sure if file_error is useful here. vcedit_error() (currently)
732 * won't give anything useful though.
733 */
734 file_error(_("Couldn't write replay gain tags for '%s': "), filename);
735 delete_temp = 1;
736 goto exit;
737 }
738
739 vcedit_clear(state);
740 state = NULL;
741 fclose(infile);
742 infile = NULL;
743
744 /* Only bother to check for close failure on the output file */
745 i = fclose(outfile);
746
747 if (i != 0)
748 {
749 file_error(_("Couldn't write replay gain tags for '%s': "), filename);
750 outfile = NULL;
751 delete_temp = 1;
752 goto exit;
753 }
754
755 outfile = NULL;
756
757 if (stat(filename, &stat_buf) != 0)
758 {
759 file_error(_("Couldn't get information about old file '%s': "), filename);
760 goto exit;
761 }
762
763 if (remove(filename) != 0)
764 {
765 file_error(_("Couldn't delete old file '%s': "), filename);
766 goto exit;
767 }
768
769 if (rename(temp_name, filename) != 0)
770 {
771 file_error(_("Couldn't rename '%s' to '%s': "), temp_name, filename);
772 goto exit;
773 }
774
775 /* TRY to copy mode and modification time... */
776
777 if (chmod(filename, stat_buf.st_mode) != 0)
778 {
779 file_error(_("Note: Couldn't set mode for file '%s': "), filename);
780 }
781
782 utime_buf.actime = stat_buf.st_atime;
783 utime_buf.modtime = stat_buf.st_mtime;
784
785 if (utime(filename, &utime_buf) != 0)
786 {
787 file_error(_("Note: Couldn't set time for file '%s': "), filename);
788 }
789
790 result = 0;
791
792 exit:
793 if (new_tags != NULL)
794 {
795 for (i = 0; i < new_count; ++i)
796 {
797 free(new_tags[i]);
798 }
799
800 free(new_tags);
801 }
802
803 if (state != NULL)
804 {
805 vcedit_clear(state);
806 }
807
808 if (infile != NULL)
809 {
810 fclose(infile);
811 }
812
813 if (infile != NULL)
814 {
815 fclose(infile);
816 }
817
818 if (delete_temp)
819 {
820 if (remove(TEMP_NAME) != 0)
821 {
822 file_error(_("Note: Couldn't remove temporary file '%s': "),
823 TEMP_NAME);
824 }
825 }
826
827 free(temp_name);
828
829 return result;
830 }
831