Skip to content

Instantly share code, notes, and snippets.

@TeaPoly
Last active December 9, 2022 07:36
Show Gist options
  • Save TeaPoly/59263912750bd17190221f4dbb7f96dd to your computer and use it in GitHub Desktop.
Save TeaPoly/59263912750bd17190221f4dbb7f96dd to your computer and use it in GitHub Desktop.
Fast way to get duration for WAVE PCM format audio.
#!/usr/bin/env python3
# Copyright 2022 Lucky Wong
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License
import wave
import contextlib
import struct
def compute_duration(path):
"""
Get duration in seconds.
:param path: a wave file
:return: duration in seconds
"""
try:
return compute_duration_from_header(path)
except:
return compute_duration_by_wave(path)
def compute_duration_from_header(path):
"""
Get duration from wave header
:param path: a wave file
:return: duration in seconds
"""
def _read_wave_raw(filename):
"""
Modified from https://gist.github.com/chief7/54873e6e7009a087180902cb1f4e27be
Just pass in a filename and get bytes representation of
audio data as result
:param filename: a wave file
:param rate:
:return: tuple -> data, #channels, samplerate, datatype (in bits)
"""
with open(filename, "rb") as wav:
# RIFF-Header: "RIFF<size as uint32_t>WAVE" (12 bytes)
riff_header = wav.read(12)
riff, filesize, wave = struct.unpack("<4sI4s", riff_header)
assert riff.decode("utf-8") == "RIFF"
assert wave.decode("utf-8") == "WAVE"
"""
Format header:
'fmt ' - 4 bytes
header length - 4 bytes
format tag - 2 bytes (only PCM supported here)
channels - 2 bytes
sample rate - 4 bytes
bytes per second - 4 bytes
block align - 2 bytes
bits per sample - 2 bytes
"""
fmt_header = wav.read(24)
fmt_header_data = struct.unpack("<4sIHHIIHH", fmt_header)
_, header_len, fmt_tag, nchannels, samplerate, _, _, dtype = fmt_header_data
assert fmt_tag == 1 # only PCM supported
"""
Data part
'data' - 4 bytes, header
len of data - 4 bytes
"""
data_header = wav.read(8)
head, data_len = struct.unpack("<4sI", data_header)
assert head.decode("utf-8") == "data"
nframes = data_len/(dtype/8)
return nchannels, samplerate, nframes
nchannels, samplerate, nframes = _read_wave_raw(path)
assert nchannels == 1
return int(nframes/samplerate)
def compute_duration_by_wave(path):
"""
Get duration using wave library
:param path: a wave file
:return: duration in seconds
"""
with contextlib.closing(wave.open(path, 'r')) as f:
channels = f.getnchannels()
assert channels == 1
nframes = f.getnframes()
framerate = f.getframerate()
return int(nframes/framerate)
if __name__ == "__main__":
import time
path = "/path/to/test.wav"
# compute_duration_by_wave: 0.034050941467285156
# compute_duration_from_header: 0.0006167888641357422
t = time.time()
d = compute_duration_by_wave(path)
print(d)
print(time.time()-t)
t = time.time()
d = compute_duration_from_header(path)
print(d)
print(time.time()-t)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment