Last active
January 19, 2023 15:46
-
-
Save mixxorz/abb8a2f22adbdb6d387f to your computer and use it in GitHub Desktop.
Generate waveform images from audio files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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() |
You're right, I didn't notice that. Updated.
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! 👍
Cool thanks! Updated.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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