views:

108

answers:

3

Hi -

I'm trying to combine two .mp3s into a single .wav file using QTKit. It seems to be working, but the last few seconds of the second file are getting truncated. Any ideas?

- (IBAction)combineSelectedFilesAndOutputAsWAV {
    QTMovie *movie = [QTMovie movieWithFile:fileOne error:NULL];
    [movie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];  
    QTMovie *segmentTwo = [QTMovie movieWithFile:fileTwo error:NULL];
    QTTimeRange range = { .time = QTZeroTime, .duration = [segmentTwo duration] };
    [segmentTwo setSelection:range];
    [movie appendSelectionFromMovie:segmentTwo];
    while([[movie attributeForKey:QTMovieLoadStateAttribute] longValue] != 100000L) {
        //wait until QTMovieLoadStateComplete
    }
    NSDictionary *exportAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:YES], QTMovieExport,
                                     [NSNumber numberWithLong:kQTFileTypeWave], QTMovieExportType, nil];
    NSString *outputFile = [NSString stringWithFormat:@"%@.wav", outputFilename];
    NSString *filepath = [destinationDirectory stringByAppendingPathComponent:outputFile];
    if (![movie writeToFile:filepath withAttributes:exportAttributes]) {
       //ERROR
    } 
}

(Ignore the while loop waiting for QTMovieLoadStateComplete. I'll switch to using notifications in the future. But for now, it shouldn't matter...)

+1  A: 

1) Generally, there is no way to quickly get exact duration of an arbitrary MP3 file. The only reliable way - decode the entire file. This is true even for CBR files (some MP3 encoders do not provide accurate bitrate). So the [segment duration] may not fit for cases, where exact duration is required. For your pupposes you can try to use something like [segment duration]+delta, or even a MaxDuration instead of [segment duration].

2) In some cases (at least if formats of merged files are the same) you can simply merge the files as binary data (without using QTKit). More pricicely: write RIFF-WAV header for target file; appeng sound stream from first file (that is source file exluding heading and tailing tags if present); appeng sound stream from second file; update RIFF-WAV header.

VitalyVal
Is there an easy way to decode the file using QTKit? If not, I can just use a delta to ensure the length is long enough for the whole file to get appended.
Stephen Poletto
I am not sure with respect to QTKit, but that can be done by Core Audio (I just start to learn Cocoa). But if possible, you should avoid decoding (at least to save processing time). I do not see a need to decode for your particular purposes. That is: simply use a huge MaxDuration, or use suggestion N 2).
VitalyVal
A: 

It looks like the real issue was that I was making queries to the QTMovie object right after it was created. Before requesting the duration of a QTMovie object, you should make sure it is fully loaded. I've added the following:

- (void)pasteSegmentToConcatenatedFile:(QTMovie *)segment {
    NSLog(@"LoadStateChanged for movie : %@", segment);
    if ([[segment attributeForKey:QTMovieLoadStateAttribute] longValue] >= kMovieLoadStateComplete) {
        NSLog(@"The movie is fully loaded.");
        QTTimeRange range = { .time = QTZeroTime, .duration = [segment duration] };
        [segment setSelection:range];
        [concatenatedFile appendSelectionFromMovie:segment];
        [self convertAndOutputConcatenatedMovie];
    }
}

This method will get invoked whenever the LoadState of the QTMovie object changes:

QTMovie *segmentTwo = [QTMovie movieWithFile:fileTwo error:NULL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pasteSegmentToConcatenatedFile:) name:QTMovieLoadStateDidChangeNotification object:segmentTwo];
Stephen Poletto