Skip to content

Instantly share code, notes, and snippets.

@mixxorz
Last active January 19, 2023 15:46
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save mixxorz/abb8a2f22adbdb6d387f to your computer and use it in GitHub Desktop.
Save mixxorz/abb8a2f22adbdb6d387f to your computer and use it in GitHub Desktop.
Generate waveform images from audio files
# Requires pydub (with ffmpeg) and Pillow
#
# Usage: python waveform.py <audio_file>
import sys
from pydub import AudioSegment
from PIL import Image, ImageDraw
class Waveform(object):
bar_count = 107
db_ceiling = 60
def __init__(self, filename):
self.filename = filename
audio_file = AudioSegment.from_file(
self.filename, self.filename.split('.')[-1])
self.peaks = self._calculate_peaks(audio_file)
def _calculate_peaks(self, audio_file):
""" Returns a list of audio level peaks """
chunk_length = len(audio_file) / self.bar_count
loudness_of_chunks = [
audio_file[i * chunk_length: (i + 1) * chunk_length].rms
for i in range(self.bar_count)]
max_rms = max(loudness_of_chunks) * 1.00
return [int((loudness / max_rms) * self.db_ceiling)
for loudness in loudness_of_chunks]
def _get_bar_image(self, size, fill):
""" Returns an image of a bar. """
width, height = size
bar = Image.new('RGBA', size, fill)
end = Image.new('RGBA', (width, 2), fill)
draw = ImageDraw.Draw(end)
draw.point([(0, 0), (3, 0)], fill='#c1c1c1')
draw.point([(0, 1), (3, 1), (1, 0), (2, 0)], fill='#555555')
bar.paste(end, (0, 0))
bar.paste(end.rotate(180), (0, height - 2))
return bar
def _generate_waveform_image(self):
""" Returns the full waveform image """
im = Image.new('RGB', (840, 128), '#f5f5f5')
for index, value in enumerate(self.peaks, start=0):
column = index * 8 + 2
upper_endpoint = 64 - value
im.paste(self._get_bar_image((4, value * 2), '#424242'),
(column, upper_endpoint))
return im
def save(self):
""" Save the waveform as an image """
png_filename = self.filename.replace(
self.filename.split('.')[-1], 'png')
with open(png_filename, 'wb') as imfile:
self._generate_waveform_image().save(imfile, 'PNG')
if __name__ == '__main__':
filename = sys.argv[1]
waveform = Waveform(filename)
waveform.save()
@mgrady3
Copy link

mgrady3 commented Oct 30, 2015

one small suggestion / observation

The class waveform defines only two instance variables in its init() method:

self.filename and self.peaks

however, as far as I can see, self.filename is never explicitly used. Instead, the filename is parsed from command line in main, passed to Waveform.init and then always just referenced as filename rather than the actual instance variable, self.filename.

I would suggest changing waveform.save() and waveform.init() to explicitly use the instance variable, otherwise the first line of the init function is essentially useless unless there was a plan to extend the class in the future with additional functionality

@mixxorz
Copy link
Author

mixxorz commented Oct 30, 2015

You're right, I didn't notice that. Updated.

@Melanpan
Copy link

Melanpan commented Nov 1, 2015

You might want to change line 67 to with open(png_filename, 'wb') as imfile:

This will make the script run on Python3 as well! 👍

@mixxorz
Copy link
Author

mixxorz commented Nov 4, 2015

Cool thanks! Updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment