71

I'm looking for a way to find out the duration of a audio file (.wav) in python. So far i had a look at python wave library, mutagen, pymedia, pymad i was not able to get the duration of the wav file. Pymad gave me the duration but its not consistent.

starball
  • 20,030
  • 7
  • 43
  • 238
Pannu
  • 2,547
  • 2
  • 23
  • 29

10 Answers10

86

The duration is equal to the number of frames divided by the framerate (frames per second):

import wave
import contextlib
fname = '/tmp/test.wav'
with contextlib.closing(wave.open(fname,'r')) as f:
    frames = f.getnframes()
    rate = f.getframerate()
    duration = frames / float(rate)
    print(duration)

Regarding @edwards' comment, here is some code to produce a 2-channel wave file:

import math
import wave
import struct
FILENAME = "/tmp/test.wav"
freq = 440.0
data_size = 40000
frate = 1000.0
amp = 64000.0
nchannels = 2
sampwidth = 2
framerate = int(frate)
nframes = data_size
comptype = "NONE"
compname = "not compressed"
data = [(math.sin(2 * math.pi * freq * (x / frate)),
        math.cos(2 * math.pi * freq * (x / frate))) for x in range(data_size)]
try:
    wav_file = wave.open(FILENAME, 'w')
    wav_file.setparams(
        (nchannels, sampwidth, framerate, nframes, comptype, compname))
    for values in data:
        for v in values:
            wav_file.writeframes(struct.pack('h', int(v * amp / 2)))
finally:
    wav_file.close()

If you play the resultant file in an audio player, you'll find that is 40 seconds in duration. If you run the code above it also computes the duration to be 40 seconds. So I believe the number of frames is not influenced by the number of channels and the formula above is correct.

wim
  • 338,267
  • 99
  • 616
  • 750
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • I had a look at all the `wave` library function but i overlooked simple logic `nframes/frame_rate`. Thank you for both the method and the code :) – Pannu Oct 20 '11 at 09:48
  • 4
    This is not quite correct ... there is a frame written for each channel, and so `duration=frames/float(rate*f.getnchannels())` – edward Sep 24 '13 at 22:18
  • 1
    @edward: I've added some code above which creates a 2-channel wave file. The formula posted in my answer computes the duration to be 40s, which jibes with what I see when I play the .wav file. So it appears to me that the number of frames does not double when you use 2-channels, and my original formula is correct. – unutbu Sep 25 '13 at 01:21
  • 3
    The contextlib stuff is not needed any more. From version 2.7 on, the `with` statement does that for you. – Lewistrick Dec 04 '13 at 11:17
  • 6
    @Lewistrick: I see `wave.open` supports the `with` statement as of [revision 84932](http://hg.python.org/cpython/rev/8327780d3841). That change affects Python3.4, but not Python2.7. As far as I can tell, [the 2.7 branch](http://hg.python.org/cpython/file/ae9fb85ab4e0/Lib/wave.py#l95) does not support the `with` statement. – unutbu Dec 04 '13 at 13:01
  • 2
    Sorry, my mistake. This was about `open`, not `wave.open`. – Lewistrick Dec 04 '13 at 15:25
  • Awesome answer yet again. – ryanjdillon Sep 29 '16 at 14:21
  • Running this code on a raspberry pi, ate up the CPU hard, would love to just be able to get the duration, then dump the info instead of holding it. – Typewar Jul 26 '17 at 21:12
60

the librosa library can do this: librosa

import librosa
librosa.get_duration(filename='my.wav')
dazzafact
  • 2,570
  • 3
  • 30
  • 49
Max
  • 695
  • 5
  • 4
  • 4
    Concise! It gives a 'second' measure (float). Thank you. – RyanC Dec 16 '20 at 05:38
  • 2
    Note that this avoids loading the contents into memory, and is therefore useful for querying the duration of long files. (quoted from librosa) – alsaleem Sep 08 '21 at 21:14
  • 1
    For what it's worth, this (and many other answers) requires `libsndfile1` to be installed on the system, which may or may not be an option (say, for a webapp, it'll depend on your host; e.g. here is [how to get libsndfile1 working on Heroku](https://stackoverflow.com/a/57746160/1717535).) – Fabien Snauwaert Jul 06 '22 at 07:36
  • The librosa-link has expired, here's the current one: https://librosa.org/ – Bernhard Wagner Nov 24 '22 at 19:06
37

A very simple method is to use soundfile (formerly pysoundfile).

Here's some example code of how to do this:

import soundfile as sf
f = sf.SoundFile('447c040d.wav')
print('samples = {}'.format(f.frames))
print('sample rate = {}'.format(f.samplerate))
print('seconds = {}'.format(f.frames / f.samplerate))

The output for that particular file is:

samples = 232569
sample rate = 16000
seconds = 14.5355625

This aligns with soxi:

Input File     : '447c040d.wav'
Channels       : 1
Sample Rate    : 16000
Precision      : 16-bit
Duration       : 00:00:14.54 = 232569 samples ~ 1090.17 CDDA sectors
File Size      : 465k
Bit Rate       : 256k
Sample Encoding: 16-bit Signed Integer PCM
Dave C
  • 1,344
  • 14
  • 14
  • 3
    For me, this is really the best answer, as it removes a lot of the complexity of the answers above. Keeping it simple! – Saelyth Mar 27 '18 at 22:37
  • 3
    Yup. And it does rely on a 3rd party library, but that 3rd party library is a simple wrapper for a well-known C library, libsndfile. And pysoundfile integrates with numpy. Win, win, win. – Dave C May 11 '20 at 17:09
  • @Airenas That's a great question: it should because `SoundFile.len()` returns `self.frames` where 1 frame contains an audio sample for every single channel. That said, I noticed that `len` API will may be deprecated: https://github.com/bastibe/python-soundfile/issues/199 Instead of `len(f)`, I would use `f.frames` per the lambda function here: https://github.com/bastibe/python-soundfile/blob/master/soundfile.py#L642 It's more clear that way. – Dave C Aug 24 '21 at 18:57
  • @Airenas I updated the answer with my suggested change per your question. I hope that clears things up. – Dave C Aug 24 '21 at 19:00
  • 1
    Easier to install and use than librosa 0.9.2. – Fabien Snauwaert Jul 21 '22 at 09:15
14

we can use ffmpeg to get the duration of any video or audio files.

To install ffmpeg follow this link

import subprocess
import re
 
process = subprocess.Popen(['ffmpeg',  '-i', path_of_wav_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
matches = re.search(r"Duration:\s{1}(?P<hours>\d+?):(?P<minutes>\d+?):(?P<seconds>\d+\.\d+?),", stdout.decode(), re.DOTALL).groupdict()
 
print(matches['hours'])
print(matches['minutes'])
print(matches['seconds'])
Saad
  • 3,340
  • 2
  • 10
  • 32
  • 2
    I got this error "cannot use a string pattern on a bytes-like object". So I replaced the call to "stdout" with "stdout.decode()" – AvielNiego Jan 13 '18 at 17:50
  • Also for Python 3 users, don't forget to have parenthesis on the print statements and as @AvielNiego pointed, use stdout.decode() than stdout. – Menilik Belay Woldeyes Aug 15 '19 at 07:33
  • Often ffprobe is available as well. It can format result output much more customized to any particular use. A bash line I use: d=$(ffprobe -i "${1}" 2>&1 | grep 'Duration') yields: "Duration: 01:06:10.61, start: 0.000000, bitrate: 787 kb/s" – Chris Reid Jan 09 '20 at 21:25
7
import os
path="c:\\windows\\system32\\loopymusic.wav"
f=open(path,"r")

#read the ByteRate field from file (see the Microsoft RIFF WAVE file format)
#https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
#ByteRate is located at the first 28th byte
f.seek(28)
a=f.read(4)

#convert string a into integer/longint value
#a is little endian, so proper conversion is required
byteRate=0
for i in range(4):
    byteRate=byteRate + ord(a[i])*pow(256,i)

#get the file size in bytes
fileSize=os.path.getsize(path)  

#the duration of the data, in milliseconds, is given by
ms=((fileSize-44)*1000)/byteRate

print "File duration in miliseconds : " % ms
print "File duration in H,M,S,mS : " % ms/(3600*1000) % "," % ms/(60*1000) % "," % ms/1000 % "," ms%1000
print "Actual sound data (in bytes) : " % fileSize-44  
f.close()
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
deAngel
  • 108
  • 3
  • A safer approach to deal with the binary contents of the file without the byteRate loop could be: from struct import unpack_from rate, = unpack_from(' – edrabc Apr 04 '13 at 13:08
  • Small mistake: `os.path.getsize(path)` should be `os.path.getsize(f)`. – Lewistrick May 14 '14 at 14:55
  • 1
    Another small mistake: `ms=((fileSize-44)*1000)/byteRate` But this is excellent because it works even if your WAV is not PCM. – Jamie Oct 06 '15 at 06:56
  • 1
    This doesn't seem to work for Python3, because open() uses encoding="utf-8" by default. Instead, use `f=open(path, encoding="latin-1")` (you don't need `"r"` because that's the default open mode). – Lewistrick Jun 20 '18 at 12:43
6

Let,T be the duration between 2 consecutive samples. So, we can write t = nT or t = n/Fs.

from scipy.io import wavfile
Fs, data = wavfile.read('filename.wav')
n = data.size
t = n / Fs
Rajan saha Raju
  • 794
  • 7
  • 13
  • 1
    in two channel wave file, this method will give double the actual answer. Use ```len(data)``` instead of ```data.size```. – Ritwik Dec 16 '20 at 09:53
6

I was trying to get the length of different format of an audio file other than '.wav' and I tried a few of the above solution but didn't work for me

This is what worked for me :

from pydub.utils import mediainfo
mediainfo('audiofile')['duration']
Aaditya Ura
  • 12,007
  • 7
  • 50
  • 88
4

To find length of music file, audioread module can be used,

install audioread: pip install audioread

then use this code:

import audioread
with audioread.audio_open(filepath) as f:
    totalsec = f.duration
    min,sec = divmod(totalsec,60) # divides total time in minute  and second 
                                    #and store it in min and sec variable respectively
3

Another solution with pydub:

import pydub
audio_seg = AudioSegment.from_wav('mywav.wav')
total_in_ms = len(audio_seg)
honederr82
  • 364
  • 1
  • 5
-2

This is short and needs no modules, works with all operating systems:

import os
os.chdir(foo) # Get into the dir with sound
statbuf = os.stat('Sound.wav')
mbytes = statbuf.st_size / 1024
duration = mbytes / 200
user9311010
  • 79
  • 11
  • 1
    Will not work in general. Assumes a particular number of bytes per sample, and a particular sample-rate. Ignores the header size. – Jon Nordby Apr 04 '21 at 14:32