1 /*
2 ** aacgain - modifications to mp3gain to support mp4/m4a files
3 ** Copyright (C) David Lasker, 2004-2007 Altos Design, Inc.
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program 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
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 **/
19 
20 //Portions of this file are derived from faad2 file frontend/main.c
21 
22 //Thanks to Prakash Punoor for help making it portable
23 
24 #include "neaacdec.h"
25 #include "aacgain.h"
26 #include "aacgaini.h"
27 //following header #includes mpeg4ip/include/mpeg4ip.h which #includes common c-lib include files
28 #include "MP4MetaFile.h"
29 
30 #ifdef WIN32
31 #include <sys/utime.h>
32 #else
33 #include <utime.h>
34 #endif
35 
36 #ifndef max
37 #define max(X,Y) ((X)>(Y)?(X):(Y))
38 #endif
39 
40 #ifndef min
41 #define min(X,Y) ((X)<(Y)?(X):(Y))
42 #endif
43 
44 #define SQRTHALF            0.70710678118654752440084436210485
45 
46 #define NEXT_SAMPLE(dest)\
47 {\
48     decode_t s;\
49     s = *sample++;\
50     dest = s * 32768.0;\
51     if (s < 0) s = -s;\
52     theGainData->peak = max(s, theGainData->peak);\
53 }
54 
55 GainDataPtr theGainData;
56 static const u_int32_t verbosity = MP4_DETAILS_ERROR|MP4_DETAILS_WARNING;
57 
58 //replay_gain tags
59 static char *RGTags[num_rg_tags] =
60 {
61     "replaygain_track_gain",
62     "replaygain_album_gain",
63     "replaygain_track_peak",
64     "replaygain_album_peak",
65     "replaygain_track_minmax",
66     "replaygain_album_minmax",
67     "replaygain_undo"
68 };
69 
70 typedef struct
71 {
72     struct stat savedAttributes;
73 } PreserveTimestamp, *PreserveTimestampPtr;
74 
modifyGain(MP4TrackId track,MP4MetaFile * mp4MetaFile,int delta,GainFixupPtr gf)75 void modifyGain(MP4TrackId track, MP4MetaFile* mp4MetaFile, int delta, GainFixupPtr gf)
76 {
77     uint8_t new_gain = gf->orig_gain + (uint8_t)delta;
78 
79     //update global_gain
80     mp4MetaFile->ModifySampleByte(track, gf->sampleId, new_gain,
81         gf->sample_offset, gf->bit_offset);
82 }
83 
AACAnalyze(void * sample_buffer,long numSamples,unsigned char channels,int compute_gain)84 static int AACAnalyze(void *sample_buffer, long numSamples, unsigned char channels,
85                       int compute_gain)
86 {
87     decode_t *samples = (decode_t *)sample_buffer;
88     decode_t *sample = samples;
89     rg_t *left_samples;
90     rg_t *right_samples;
91     long i;
92 
93     left_samples = new rg_t[numSamples];
94     if (!left_samples)
95         return 1;
96     if ((channels == 2) || (channels == 6))
97     {
98         right_samples = new rg_t[numSamples];
99     } else {
100         right_samples = NULL;
101     }
102 
103     switch (channels)
104     {
105     case 1:
106         for (i=0; i<numSamples; i++)
107         {
108             NEXT_SAMPLE(left_samples[i])
109         }
110         break;
111     case 2:
112         for (i=0; i<numSamples; i++)
113         {
114             NEXT_SAMPLE(left_samples[i])
115             NEXT_SAMPLE(right_samples[i])
116         }
117        break;
118     case 6:
119         for (i=0; i<numSamples; i++)
120         {
121             //faad2 gives samples in following order: c,l,r,bl,br,lfe
122             decode_t c, l, r, bl, br, lfe;
123             NEXT_SAMPLE(c);
124             NEXT_SAMPLE(l);
125             NEXT_SAMPLE(r);
126             NEXT_SAMPLE(bl);
127             NEXT_SAMPLE(br);
128             NEXT_SAMPLE(lfe);
129             left_samples[i] = l + c*SQRTHALF + bl*SQRTHALF + lfe;
130             right_samples[i] = r + c*SQRTHALF + br*SQRTHALF + lfe;
131         }
132         break;
133     default:
134         for (i=0; i<numSamples; i++)
135         {
136             int j;
137             decode_t sum = 0;
138             for (j=0; j<channels; j++)
139             {
140                 decode_t samp;
141                 NEXT_SAMPLE(samp)
142                 sum += samp;
143             }
144             left_samples[i] = (rg_t)sum / (rg_t)channels;
145         }
146     }
147 
148     if (compute_gain)
149         AnalyzeSamples(left_samples, right_samples, numSamples, right_samples ? 2 : 1);
150 
151     delete [] left_samples;
152     if (right_samples)
153         delete [] right_samples;
154 
155     return 0;
156 }
157 
parseMp4File(GainDataPtr gd,ProgressCallback reportProgress,int compute_gain)158 static int parseMp4File(GainDataPtr gd, ProgressCallback reportProgress, int compute_gain)
159 {
160     NeAACDecHandle hDecoder = gd->hDecoder;
161     unsigned char *buffer;
162     unsigned int buffer_size;
163     void *sample_buffer;
164     NeAACDecFrameInfo frameInfo;
165     MP4MetaFile* mp4MetaFile = (MP4MetaFile*)gd->mp4MetaFile;
166 
167     unsigned long sampleId, numSamples;
168     int percent, old_percent = -1;
169 
170     theGainData = gd;
171     gd->GainHead = gd->GainTail = NULL;
172     frameInfo.error = 0;
173     numSamples = mp4MetaFile->GetTrackNumberOfSamples(gd->track);
174 
175     for (sampleId = 1; sampleId <= numSamples; sampleId++)
176     {
177         /* get acces unit from MP4 file */
178         buffer = NULL;
179         buffer_size = 0;
180 
181         //set sampleId for use by syntax.c
182         gd->sampleId = sampleId;
183 
184         try
185         {
186             mp4MetaFile->ReadSample(gd->track, sampleId, (u_int8_t**)(&buffer), (u_int32_t*)(&buffer_size));
187         } catch (MP4Error* e)
188         {
189             e->Print();
190             fprintf(stderr, "Reading from MP4 file failed. \n");
191             NeAACDecClose(hDecoder);
192             free (e);
193             return 1;
194         }
195 
196         sample_buffer = aacgainDecode(hDecoder, &frameInfo, buffer, buffer_size);
197         if (gd->analyze && (frameInfo.error == 0) && (frameInfo.samples > 0))
198 
199         {
200             AACAnalyze(sample_buffer, frameInfo.samples/gd->channels, gd->channels, compute_gain);
201         }
202 
203         if (buffer) free(buffer);
204 
205         percent = min((int)(sampleId*100)/numSamples, 100);
206         if (reportProgress && (percent > old_percent))
207         {
208             old_percent = percent;
209             reportProgress(percent, (unsigned int)mp4MetaFile->GetFileSize());
210         }
211 
212 		//ignore error 4 (scalefactor out of range) which seems to happen on some tracks
213 		//other errors are fatal
214         if ((frameInfo.error > 0) && (frameInfo.error!=4))
215         {
216             fprintf(stderr, "Error: invalid file format %s, code=%d\n",
217                 gd->mp4file_name, frameInfo.error);
218             gd->abort = 1;
219             break;
220         }
221     }
222 
223     return frameInfo.error;
224 }
225 
PrepareToWrite(GainDataPtr gd)226 static MP4MetaFile *PrepareToWrite(GainDataPtr gd)
227 {
228     MP4MetaFile* mp4MetaFile = (MP4MetaFile*)gd->mp4MetaFile;
229 
230     if (!gd->open_for_write)
231     {
232 		if (gd->use_temp)
233 		{
234 			//if we are using a temp file, create it now...
235 			gd->temp_name = mp4MetaFile->TempFileName(gd->mp4file_name);
236 			FILE *tmpFile = fopen(gd->temp_name, "wb");
237 			if (!tmpFile)
238 			{
239 				fprintf(stderr, "Error: unable to create temporary file %s\n", gd->temp_name);
240 				exit(1);
241 			}
242 			//close the MP4MetaFile and reopen as stdio file
243 			mp4MetaFile->Close();
244 			delete mp4MetaFile;
245 			FILE *inFile = fopen(gd->mp4file_name, "rb");
246 			if (!inFile)
247 			{
248 				fprintf(stderr, "Error: unable to reopen file %s to create temporary file\n",
249 					gd->mp4file_name);
250 				exit(1);
251 			}
252 
253 			//copy the original file to the temp file
254 			static const u_int32_t blockSize = 4096;
255 			u_int8_t *buffer = new u_int8_t[blockSize];
256 			for (;;)
257 			{
258 				int bytesRead = fread(buffer, 1, blockSize, inFile);
259 				if (bytesRead)
260 					fwrite(buffer, 1, bytesRead, tmpFile);
261 				if (bytesRead < blockSize)
262 					break;
263 			}
264 			fclose(inFile);
265 			fclose(tmpFile);
266 			delete buffer;
267 			try
268 			{
269 				gd->mp4MetaFile = mp4MetaFile = new MP4MetaFile(verbosity);
270 				mp4MetaFile->Modify(gd->temp_name);
271 			} catch(MP4Error *e) {
272 				fprintf(stderr, "Unable to open file %s for writing.\n", gd->temp_name);
273 				free(e);
274 				exit(1);
275 			}
276 		} else {
277 			//otherwise open the file for writing
278 			try
279 			{
280 				mp4MetaFile->Close();
281 				delete mp4MetaFile;
282 				gd->mp4MetaFile = mp4MetaFile = new MP4MetaFile(verbosity);
283 				mp4MetaFile->Modify(gd->mp4file_name);
284 			} catch(MP4Error *e) {
285 				fprintf(stderr, "Unable to open file %s for writing. It may be in use\n"
286 					"by another program\n", gd->mp4file_name);
287 				free(e);
288 				exit(1);
289 			}
290 		}
291         gd->open_for_write = 1;
292     }
293 
294     return mp4MetaFile;
295 }
296 
aac_open(char * mp4_file_name,int use_temp,int preserve_timestamp,AACGainHandle * gh)297 int aac_open(char *mp4_file_name, int use_temp, int preserve_timestamp, AACGainHandle *gh)
298 {
299     FILE* mp4_file;
300     GainDataPtr gd;
301     unsigned char header[8];
302     size_t file_name_len;
303     MP4MetaFile* mp4MetaFile;
304     PreserveTimestampPtr pt = NULL;
305 
306     *gh = NULL;
307 
308     //In order to allow processed files to play on iPod Shuffle, which is extremely sensitive to
309     // file format, we always use a temp file. This runs the MP4File::Optimize function,
310     // which rewrites the processed file in the canonical order.
311     use_temp = true;
312 
313     file_name_len = strlen(mp4_file_name);
314     if ((file_name_len >= 5) && (strcmp(mp4_file_name + file_name_len - 4, ".m4p") == 0))
315     {
316         fprintf(stderr, "Error: DRM protected file %s is not supported.\n", mp4_file_name);
317         return 1;
318     }
319 
320     mp4_file = fopen(mp4_file_name, "rb");
321     if (!mp4_file)
322     {
323         //caller's responsibility to give error message so we can use aac_open to test for aac file
324         return 0;
325     }
326 
327     fread(header, 1, 8, mp4_file);
328     fclose(mp4_file);
329     if (header[4] != 'f' || header[5] != 't' || header[6] != 'y' || header[7] != 'p')
330     {
331         //no error - use this to tell if a file is mp3 or mp4
332         return 0;
333     }
334 
335     if (preserve_timestamp)
336     {
337         pt = new PreserveTimestamp;
338         stat(mp4_file_name, &pt->savedAttributes);
339     }
340 
341     gd = new GainData;
342 	gd->track = MP4_INVALID_TRACK_ID;
343     gd->mp4MetaFile = NULL;
344     gd->analyze = 0;
345     gd->use_temp = use_temp;
346     gd->open_for_write = 0;
347     gd->gain_read = 0;
348     gd->peak = 0;
349     gd->hDecoder = NULL;
350     gd->abort = 0;
351     gd->preserve_timestamp = pt;
352     gd->GainHead = NULL;
353 
354     gd->mp4file_name = strdup(mp4_file_name);
355     gd->temp_name = NULL;
356 
357     unsigned char *buffer = NULL;
358     unsigned int buffer_size = 0;
359 
360     try
361     {
362         mp4MetaFile = new MP4MetaFile(verbosity);
363         mp4MetaFile->Read(mp4_file_name);
364         if (mp4MetaFile->GetNumberOfTracks(MP4_AUDIO_TRACK_TYPE) != 1)
365         {
366             fprintf(stderr, "File must contain a single audio track.\n");
367             throw new MP4Error();
368         }
369         gd->free_atom_size = (u_int32_t)mp4MetaFile->GetFreeAtomSize();
370         gd->mp4MetaFile = mp4MetaFile;
371 
372 		/* Find the first audio track, store in GainData struct. */
373 		gd->track = mp4MetaFile->FindTrackId(0, MP4_AUDIO_TRACK_TYPE);
374         mp4MetaFile->GetTrackESConfiguration(gd->track, (u_int8_t**)(&buffer), (u_int32_t*)(&buffer_size));
375     } catch (MP4Error* e)
376     {
377         /* unable to open file */
378         fprintf(stderr, "Error opening file: %s\n", gd->mp4file_name);
379         gd->abort = 1;
380         aac_close(gd);
381         free (e);
382         return 1;
383     }
384 
385     NeAACDecHandle hDecoder;
386     NeAACDecConfigurationPtr config;
387     mp4AudioSpecificConfig mp4ASC;
388 
389     hDecoder = gd->hDecoder = NeAACDecOpen();
390 
391     /* Set configuration */
392     config = NeAACDecGetCurrentConfiguration(hDecoder);
393     config->outputFormat = FAAD_FMT_DOUBLE;
394     config->downMatrix = 0;
395     NeAACDecSetConfiguration(hDecoder, config);
396 
397     if (NeAACDecInit2(hDecoder, buffer, buffer_size,
398                     &gd->samplerate, &gd->channels) < 0)
399     {
400         /* If some error initializing occurred, skip the file */
401         if (buffer)
402             free(buffer);
403         fprintf(stderr, "Error: file format not recognized.\n");
404         gd->abort = 1;
405         aac_close(gd);
406         return 1;
407     }
408 
409     if (NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC) >= 0)
410     {
411         if (mp4ASC.sbr_present_flag == 1)
412         {
413             free(buffer);
414             fprintf(stderr, "Error: HE_AAC/SBR files are not supported.\n");
415             gd->abort = 1;
416             aac_close(gd);
417             return 1;
418         }
419     }
420     free(buffer);
421 
422     *gh = gd;
423     return 0;
424 }
425 
aac_get_sample_rate(AACGainHandle gh)426 unsigned int aac_get_sample_rate(AACGainHandle gh)
427 {
428     GainDataPtr gd = (GainDataPtr)gh;
429 
430     return gd->samplerate;
431 }
432 
aac_compute_gain(AACGainHandle gh,rg_t * peak,unsigned char * min_gain,unsigned char * max_gain,ProgressCallback reportProgress)433 int aac_compute_gain(AACGainHandle gh, rg_t *peak, unsigned char *min_gain,
434                      unsigned char *max_gain, ProgressCallback reportProgress)
435 {
436     int rc = 0;
437     GainDataPtr gd = (GainDataPtr)gh;
438 
439     if (!gd->gain_read || !gd->analyze)
440     {
441 	    gd->analyze = 1;
442         gd->peak = 0;
443         gd->min_gain = 255;
444         gd->max_gain = 0;
445         rc = parseMp4File(gd, reportProgress, 1);
446         gd->gain_read = 1;
447     }
448     if (peak)
449     {
450         *peak = gd->peak * 32768.0;
451     }
452     if (min_gain)
453         *min_gain = gd->min_gain;
454     if (max_gain)
455         *max_gain = gd->max_gain;
456 
457     return rc;
458 }
459 
aac_compute_peak(AACGainHandle gh,rg_t * peak,unsigned char * min_gain,unsigned char * max_gain,ProgressCallback reportProgress)460 int aac_compute_peak(AACGainHandle gh, rg_t *peak, unsigned char *min_gain,
461                      unsigned char *max_gain, ProgressCallback reportProgress)
462 {
463     int rc = 0;
464     GainDataPtr gd = (GainDataPtr)gh;
465 
466     if (!gd->gain_read || !gd->analyze)
467     {
468 		gd->analyze = 1;
469         gd->peak = 0;
470         gd->min_gain = 255;
471         gd->max_gain = 0;
472         rc = parseMp4File(gd, reportProgress, 0);
473         gd->gain_read = 1;
474     }
475     if (peak)
476     {
477         *peak = gd->peak * 32768.0;
478     }
479     if (min_gain)
480         *min_gain = gd->min_gain;
481     if (max_gain)
482         *max_gain = gd->max_gain;
483 
484     return rc;
485 }
486 
aac_modify_gain(AACGainHandle gh,int left,int right,ProgressCallback reportProgress)487 int aac_modify_gain(AACGainHandle gh, int left, int right,
488                     ProgressCallback reportProgress)
489 {
490     GainDataPtr gd = (GainDataPtr)gh;
491     MP4MetaFile* mp4MetaFile = (MP4MetaFile*)gd->mp4MetaFile;
492     GainFixupPtr gf;
493     int rc = 0;
494 
495     if ((gd->channels != 2) && (left != right))
496     {
497         fprintf(stderr, "Error: individual channel adjustments are only supported on\n"
498             "2-channel (stereo) files.\n");
499         gd->abort = 1;
500         return -1;
501     }
502 
503     if (!gd->gain_read)
504     {
505         gd->analyze = 0;
506         rc = parseMp4File(gd, reportProgress, 0);
507         gd->gain_read = 1;
508         if (rc)
509         {
510             gd->abort = 1;
511             return rc;
512         }
513     }
514 
515     //test for wrap before modifying file
516     gf = gd->GainHead;
517     while (gf)
518     {
519         if (((gf->channel == 0) &&
520             (((gf->orig_gain + left) < 0) || ((gf->orig_gain + left) > 255))) ||
521             ((gf->channel == 1) &&
522             (((gf->orig_gain + right) < 0) || ((gf->orig_gain + right) > 255))))
523         {
524             fprintf(stderr, "Error: Wrap while modifying gain.\n");
525             gd->abort = 1;
526             return -1;
527         }
528         gf = gf->next;
529     }
530 
531     mp4MetaFile = PrepareToWrite(gd);
532 
533     gf = gd->GainHead;
534     while (gf)
535     {
536         GainFixupPtr prev;
537 
538 		//update global_gain
539         modifyGain(gd->track, mp4MetaFile, (gf->channel == 0) ? left : right, gf);
540         prev = gf;
541         gf = gf->next;
542         free(prev);
543     }
544     gd->GainHead = NULL;
545 
546     return rc;
547 }
548 
aac_set_tag_float(AACGainHandle gh,rg_tag_e tag,rg_t value)549 int aac_set_tag_float(AACGainHandle gh, rg_tag_e tag, rg_t value)
550 {
551     GainDataPtr gd = (GainDataPtr)gh;
552     MP4MetaFile* mp4MetaFile = PrepareToWrite(gd);
553     char vstr[20];
554 
555     sprintf(vstr, "%-.2f", value);
556     mp4MetaFile->SetMetadataFreeForm(RGTags[tag], (u_int8_t*)vstr, strlen(vstr));
557 
558     return 0;
559 }
560 
aac_get_tag_float(AACGainHandle gh,rg_tag_e tag,rg_t * value)561 int aac_get_tag_float(AACGainHandle gh, rg_tag_e tag, rg_t *value)
562 {
563     GainDataPtr gd = (GainDataPtr)gh;
564     MP4MetaFile* mp4MetaFile = (MP4MetaFile*)gd->mp4MetaFile;
565     char *vstr;
566 	u_int32_t vsize;
567 
568     if (mp4MetaFile->GetMetadataFreeForm(RGTags[tag], (u_int8_t**)&vstr, &vsize))
569     {
570 		//null terminate the value
571 		vstr = (char*)realloc(vstr, vsize+1);
572 		vstr[vsize] = '\0';
573 
574 		sscanf(vstr, "%lf", value);
575         free(vstr);
576         return 0;
577     }
578 
579     return 1;
580 }
581 
aac_set_tag_int_2(AACGainHandle gh,rg_tag_e tag,int p1,int p2)582 int aac_set_tag_int_2(AACGainHandle gh, rg_tag_e tag, int p1, int p2)
583 {
584     GainDataPtr gd = (GainDataPtr)gh;
585     MP4MetaFile* mp4MetaFile = PrepareToWrite(gd);
586 
587     char vstr[100];
588 
589     sprintf(vstr, "%d,%d", p1, p2);
590     mp4MetaFile->SetMetadataFreeForm(RGTags[tag], (u_int8_t*)vstr, strlen(vstr));
591 
592     return 0;
593 }
594 
aac_get_tag_int_2(AACGainHandle gh,rg_tag_e tag,int * p1,int * p2)595 int aac_get_tag_int_2(AACGainHandle gh, rg_tag_e tag, int *p1, int *p2)
596 {
597     GainDataPtr gd = (GainDataPtr)gh;
598     MP4MetaFile* mp4MetaFile = (MP4MetaFile*)gd->mp4MetaFile;
599     char *vstr;
600 	u_int32_t vsize;
601 
602     if (mp4MetaFile->GetMetadataFreeForm(RGTags[tag], (u_int8_t**)&vstr, &vsize))
603     {
604 		//null terminate the value
605 		vstr = (char*)realloc(vstr, vsize+1);
606 		vstr[vsize] = '\0';
607 
608 		sscanf(vstr, "%d,%d", p1, p2);
609         free(vstr);
610         return 0;
611     }
612 
613     return 1;
614 }
615 
aac_clear_rg_tags(AACGainHandle gh)616 int aac_clear_rg_tags(AACGainHandle gh)
617 {
618     GainDataPtr gd = (GainDataPtr)gh;
619     MP4MetaFile* mp4MetaFile = PrepareToWrite(gd);
620     uint32_t i;
621 
622     for (i=0; i<num_rg_tags; i++)
623     {
624         mp4MetaFile->DeleteMetadataFreeForm(RGTags[i]);
625     }
626 
627     return 0;
628 }
629 
aac_close(AACGainHandle gh)630 int aac_close(AACGainHandle gh)
631 {
632     GainDataPtr gd = (GainDataPtr)gh;
633     MP4MetaFile* mp4MetaFile = (MP4MetaFile*)gd->mp4MetaFile;
634     int rc = 0;
635     PreserveTimestampPtr pt = (PreserveTimestampPtr)gd->preserve_timestamp;
636     const char *tempFileName = NULL;
637 
638     //close the faad decoder if open
639     if (gd->hDecoder)
640     {
641         NeAACDecClose(gd->hDecoder);
642     }
643 
644     //delete the gain change linked list if present
645     while (gd->GainHead)
646     {
647         GainFixupPtr next = gd->GainHead->next;
648         free(gd->GainHead);
649         gd->GainHead = next;
650     }
651 
652     if (mp4MetaFile)
653     {
654         if (gd->use_temp && gd->temp_name)
655             tempFileName = mp4MetaFile->TempFileName(gd->mp4file_name);
656 
657         mp4MetaFile->Close();
658         delete mp4MetaFile;
659     }
660 
661     if (tempFileName)
662     {
663         if (!gd->abort)
664         {
665             //use MP4File::Optimize to undo the wasted space created by MP4File::Modify
666             //send optimize output to a temp file "just in case"
667             MP4MetaFile f;
668             f.Optimize(gd->temp_name, tempFileName, gd->free_atom_size);
669 
670             //rename the temp file back to original name
671             int rc = remove(gd->mp4file_name);
672             if (rc == 0)
673                 rc = rename(tempFileName, gd->mp4file_name);
674             if (rc)
675                 fprintf(stderr, "Error: attempt to create file %s failed. Your output file is named %s",
676                     gd->mp4file_name, tempFileName);
677             free((void*)tempFileName);
678         }
679         remove(gd->temp_name);
680         free((void*)gd->temp_name);
681     }
682 
683     if (pt)
684     {
685         if (!gd->abort)
686         {
687 			struct utimbuf setTime;
688 
689 			setTime.actime = pt->savedAttributes.st_atime;
690 			setTime.modtime = pt->savedAttributes.st_mtime;
691 			utime(gd->mp4file_name, &setTime);
692         }
693         delete pt;
694     }
695 
696     free(gd->mp4file_name);
697     delete gd;
698 
699     return rc;
700 }