Skip to content

Instantly share code, notes, and snippets.

@thevickypedia
Last active November 29, 2023 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thevickypedia/e902b04e7bf3517ca35cb76b8c88c17f to your computer and use it in GitHub Desktop.
Save thevickypedia/e902b04e7bf3517ca35cb76b8c88c17f to your computer and use it in GitHub Desktop.
Play audio files using pyaudio module
import mimetypes
import os
import wave
from threading import Thread
from typing import NoReturn, Union
import pyaudio
class PlayAudio(Thread):
"""Instantiates ``PlayAudio`` object to play an audio wav file.
>>> PlayAudio
"""
def __init__(self, filename: Union[os.PathLike, str]):
"""Initializes the necessary args.
Args:
filename: Takes the audio filename as an argument.
"""
if not os.path.isfile(filename):
raise FileNotFoundError(
f"{filename} not found."
)
file_type = mimetypes.guess_type(url=filename, strict=True)[0]
_, file_extension = os.path.splitext(filename)
if file_type != "audio/x-wav" and file_extension != ".wav":
raise FileExistsError(
"PlayAudio module can only be used for .wav files."
)
super().__init__()
# length of data to read.
self.chunk = 1024
# open the file for reading.
self.wave_file = wave.open(f=filename, mode='rb')
# create an audio object
self.py_audio = pyaudio.PyAudio()
# open stream based on the wave object which has been input.
self.stream = self.py_audio.open(
format=self.py_audio.get_format_from_width(width=self.wave_file.getsampwidth()),
channels=self.wave_file.getnchannels(), rate=self.wave_file.getframerate(), output=True
)
def play(self) -> NoReturn:
"""Reads the data based on chunk size and plays the stream while writing to the stream."""
# read data (based on the chunk size)
data = self.wave_file.readframes(nframes=self.chunk)
# play stream (looping from beginning of file to the end)
while data:
# writing to the stream is what *actually* plays the sound.
self.stream.write(frames=data)
data = self.wave_file.readframes(nframes=self.chunk)
self.close()
def close(self) -> NoReturn:
"""Closes the wav file and resources blocked by pyaudio."""
self.wave_file.close()
self.py_audio.close(stream=self.stream)
self.stream.close()
self.py_audio.terminate()
def run(self) -> NoReturn:
"""Override built-in."""
self.play()
def playsound(sound: Union[os.PathLike, str], block: bool = True) -> NoReturn:
"""Triggers the ``PlayAudio`` object.
Args:
sound: Takes the filename as the argument.
block: Takes an argument whether to run as a thread to block the current process or not.
Warnings:
- Beware of specifying ``block=False`` when combining with other processes that use pyaudio resources.
"""
player = PlayAudio(filename=str(sound))
player.run() if block else player.start()
def _convert_sound_file(filepath: Union[os.PathLike, str], samplerate: int = 16_000) -> NoReturn:
"""Helper function to reform wav files if ``PyAudio`` module fails to decode a wav file generated by ``pyttsx3``.
Args:
filepath: Filename or filepath of the wav file.
samplerate: Sample rate of the audio file.
"""
try:
import librosa # noqa: PyPackageRequirements
except ImportError:
raise ImportError("install librosa==0.9.2")
try:
import soundfile # noqa: PyPackageRequirements
except ImportError:
raise ImportError("install soundfile==0.11.0")
data, _ = librosa.load(path=filepath, sr=samplerate)
soundfile.write(file=filepath, data=data, samplerate=samplerate)
wave.open(f=filepath, mode='r')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment