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, &current_section);
421 #else
422         ret = ov_read_float(&vf, &pcm, 1024, &current_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