61
NSDictionary* fileAttributes = 
    [[NSFileManager defaultManager] attributesOfItemAtPath:filename 
                                                     error:nil]

From the file attribute keys, you can get the date, size, etc. But how do you get the duration?

Chris Markle
  • 2,076
  • 4
  • 25
  • 46
Namratha
  • 16,630
  • 27
  • 90
  • 125
  • 1
    Where are you getting the NSDictionary from? – James Bedford Mar 07 '11 at 11:55
  • 1
    it's a built in data type in iOS. it contains key value pairs for date, size etc for a file. – Namratha Mar 15 '11 at 09:15
  • 2
    I know what an NSDictionary is. Where are you retrieving the instance of this NSDictionary from? You need to give a lot more information about what you're trying to do in your question. – James Bedford Mar 15 '11 at 09:36
  • 1
    p.s. An NSDictionary does not necessarily contain values for the data and size of a file in particular, it just contains a 1-to-1 mapping of a set of keys to a set of values. – James Bedford Mar 15 '11 at 09:37
  • 1
    I've edited my question to include that info. Thanks and sorry for the inconvenience. – Namratha Mar 15 '11 at 09:42
  • the duration doesn't seem to be part of the NSDictionary that is returned by attributesOfItemAtPath – Namratha Mar 15 '11 at 09:44

9 Answers9

146

In the 'File Attribute Keys' of the NSFileManager class reference you can see that there is no key to use that will return the duration of a song. All the information that the NSFileManager instance gets about a file is to do with the properties of the actual file itself within the operating system, such as its file-size. The NSFileManager doesn't actually interpret the file.

In order to get the duration of the file, you need to use a class that knows how to interpret the file. The AVFoundation framework provides the exact class you need, AVAsset. You can instantiate an instance of this abstract class using the concrete subclass AVURLAsset, and then provide it an NSURL which points to the audio file you wish to get the duration. You can then get the duration from the AVAsset instance by querying its duration property.

For example:

AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioFileURL options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);

Note that AVFoundation is designed as a heavily asynchronous framework in order to improve performance and the overall user experience. Even performing simple tasks such as querying a media file's duration can potentially take a long period of time and can cause your application to hang. You should use the AVAsynchronousKeyValueLoading protocol to asynchronously load the duration of the song, and then update your UI in a completion handler block. You should take a look at the 'Block Programming Guide' as well as the WWDC2010 video titled, 'Discovering AV Foundation', which is available free at https://developer.apple.com/videos/wwdc/2010.

Community
  • 1
  • 1
James Bedford
  • 28,702
  • 8
  • 57
  • 64
  • 16
    i always get 0 duration – OMGPOP Mar 11 '14 at 03:22
  • This way of doing it gives me error: `FigByteFlumeCustomURLOpen signalled err=-12936 (kFigByteFlumeError_BadState) (no provider) at /SourceCache/CoreMedia/CoreMedia-1562.235/Prototypes/FigHTTP/FigByteFlumeCustomURL.c line 1486`. Answer by John Goodstadt below works for me though. – Maksim May 01 '15 at 10:06
  • Excellent answer! Very concise way of getting the duration without creating an audio player instance. – Christian Gossain May 16 '15 at 21:38
  • Such a way to get duration might return an invalid value. Always wraps your `AVAsset` and `AVAssetTrack` accessing with methods in `AVAsynchronousKeyValueLoading`. – WeZZard Sep 14 '15 at 15:59
  • 2
    Make sure to also add and import the CoreMedia framework in addition to AVFoundation. Thanks for this effective method, James. – Erik Feb 05 '16 at 12:34
  • Thanks for the answer but the problem is it is giving me some wrong duration. Like for 30 seconds audio, it is coming as 37 seconds. Any idea? – Swayambhu May 21 '16 at 05:41
  • @OMGPOP I suppose your url is an http/https url?I have tried local file url can work – Juice007 Apr 01 '20 at 07:17
  • You can only get accurate duration result if you wait for the result to load. You can only do that asynchronously. This is why the .loadValuesAsynchronously() is needed, otherwise (to my understanding) it will return the value that the phone was able to calculate / read in a short amount of time. The bigger the audio, the more seconds will be off by the true duration. – nCr78 Jun 18 '21 at 23:23
19

For anyone still looking for this. Based on the answer, the code for Swift 4 (including the async loading of values taken from Apple's documentation):

let audioAsset = AVURLAsset.init(url: yourURL, options: nil)

audioAsset.loadValuesAsynchronously(forKeys: ["duration"]) {
    var error: NSError? = nil
    let status = audioAsset.statusOfValue(forKey: "duration", error: &error)
    switch status {
    case .loaded: // Sucessfully loaded. Continue processing.
        let duration = audioAsset.duration
        let durationInSeconds = CMTimeGetSeconds(duration)
        print(Int(durationInSeconds))
        break              
    case .failed: break // Handle error
    case .cancelled: break // Terminate processing
    default: break // Handle all other cases
    }
}
nCr78
  • 460
  • 7
  • 9
16

You can achieve the same in Swift using :

let audioAsset = AVURLAsset.init(url: audioFileURL, options: nil)
let duration = audioAsset.duration
let durationInSeconds = CMTimeGetSeconds(duration)
Ashildr
  • 1,783
  • 3
  • 16
  • 25
14

For completeness - There is another way to get the duration for a mp3 file:

NSURL * pathToMp3File = ...
NSError *error = nil;
AVAudioPlayer* avAudioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:pathToMp3File error:&error];

double duration = avAudioPlayer.duration; 
avAudioPlayer = nil;

I have used this with no discernible delay.

Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
John Goodstadt
  • 678
  • 8
  • 13
10

Swift 5.0 + iOS 13: This is the only way it worked for me (@John Goodstadt solution in Swift). Currently I'm not sure why, but the there is a difference of average 0.2 seconds between a recorded audio file (in my case a voice memo) and the received audio file using the following code.

    do {
        let audioPlayer = try AVAudioPlayer(contentsOf: fileURL)
        return CGFloat(audioPlayer.duration)
    } catch {
        assertionFailure("Failed crating audio player: \(error).")
        return nil
    }
Baran
  • 2,710
  • 1
  • 23
  • 23
2

iOS 15+ solution

If you can, try this newer method:

let asset = AVURLAsset(url: fileURL, options: nil)

// Returns a CMTime value.
let duration = try await asset.load(.duration)

// An array of AVMetadataItem for the asset.
let metadata = try await asset.load(.metadata)

// A CMTime value and an array of AVMetadataItem.
let (duration, metadata) = try await asset.load(.duration, .metadata)

Note: Loading several properties at the same time enables AVFoundation to optimize performance by batch-loading requests.

Here's a neat guide from Apple documentation Loading media data asynchronously.

Community
  • 1
  • 1
Ajith Renjala
  • 4,934
  • 5
  • 34
  • 42
1

I record a linear PCM file (.pcm) through AVAudioRecorder. I get the duration with the help of Farhad Malekpour. Maybe this can help you : iPhone: get duration of an audio file

NSURL *fileUrl = [NSURL fileURLWithPath:yourFilePath];
AudioFileID fileID;
OSStatus result = AudioFileOpenURL((__bridge CFURLRef)fileUrl,kAudioFileReadPermission, 0, &fileID);
Float64 duration = 0; //seconds. the duration of the audio.
UInt32 ioDataSize = sizeof(Float64);

result = AudioFileGetProperty(fileID, kAudioFilePropertyEstimatedDuration, 
&ioDataSize, &duration);
AudioFileClose(fileID);
if(0 == result) {
   //success
}
else {
  switch (result) {
    case kAudioFileUnspecifiedError:{
        //
    } break;
        // other cases...
    default:
        break;
  }
}
guozqzzu
  • 839
  • 10
  • 12
1

Use AVAssetReader to get the duration of the audio file

guard let assetReader = try? AVAssetReader(asset: audioAsset) {
    return nil
}
let duration = Double(assetReader.asset.duration.value)
let timescale = Double(assetReader.asset.duration.timescale)
let totalDuration = duration / timescale
print(totalDuration)
-1

I actually found that you can just use the “get details from music” block and set it to “get duration of ” where can be an mp3 input from a prompt, a link to an mp3, an mp3 from the share sheet, etc