Search code examples
objective-ccore-audioqtkit

Combine and convert MP3 files using QTKit


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...)


Solution

  • 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];