2023-02-08 01:59:02 +00:00
|
|
|
#! /usr/bin/env python3
|
|
|
|
|
2023-10-09 20:11:45 +00:00
|
|
|
import logging
|
2023-02-08 01:59:02 +00:00
|
|
|
import time
|
|
|
|
import wave
|
2023-10-09 20:11:45 +00:00
|
|
|
from typing import List
|
|
|
|
|
|
|
|
import pyaudio
|
2023-02-08 01:59:02 +00:00
|
|
|
from pydub import AudioSegment
|
2023-10-09 20:11:45 +00:00
|
|
|
from pydub.effects import compress_dynamic_range, normalize
|
2023-02-08 01:59:02 +00:00
|
|
|
from pydub.scipy_effects import band_pass_filter
|
|
|
|
|
2023-10-09 20:11:45 +00:00
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
logger = logging.getLogger(__name__)
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AudioInterface:
|
2023-10-09 20:11:45 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
hook,
|
|
|
|
buffer_size,
|
|
|
|
channels,
|
|
|
|
format,
|
|
|
|
sample_rate,
|
|
|
|
recording_limit,
|
|
|
|
dev_index,
|
|
|
|
hook_type,
|
|
|
|
) -> None:
|
|
|
|
self.chunk = buffer_size
|
|
|
|
self.chans = channels
|
|
|
|
self.format = format
|
|
|
|
self.frames: List[bytes] = []
|
2023-02-08 01:59:02 +00:00
|
|
|
self.hook = hook
|
2023-10-09 20:11:45 +00:00
|
|
|
self.samp_rate = sample_rate
|
|
|
|
self.recording_limit = recording_limit
|
|
|
|
self.dev_index = dev_index
|
|
|
|
self.hook_type = hook_type
|
|
|
|
|
|
|
|
self.audio = None
|
|
|
|
self.stream = None
|
|
|
|
|
|
|
|
def init_audio(self):
|
|
|
|
if self.audio is None:
|
|
|
|
self.audio = pyaudio.PyAudio()
|
|
|
|
if self.stream is not None:
|
|
|
|
self.stream.stop_stream()
|
|
|
|
self.stream.close()
|
|
|
|
self.stream = None
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
def record(self):
|
2023-10-09 20:11:45 +00:00
|
|
|
self.init_audio()
|
2023-02-08 01:59:02 +00:00
|
|
|
self.stream = self.audio.open(
|
|
|
|
format=self.format,
|
|
|
|
rate=self.samp_rate,
|
|
|
|
channels=self.chans,
|
|
|
|
input_device_index=self.dev_index,
|
|
|
|
input=True,
|
|
|
|
frames_per_buffer=self.chunk,
|
|
|
|
)
|
2023-10-09 20:11:45 +00:00
|
|
|
|
2023-02-08 01:59:02 +00:00
|
|
|
# loop through stream and append audio chunks to frame array
|
|
|
|
try:
|
|
|
|
start = time.time()
|
2023-10-09 20:11:45 +00:00
|
|
|
while self.off_hook_condition():
|
2023-02-08 01:59:02 +00:00
|
|
|
if time.time() - start < self.recording_limit:
|
|
|
|
data = self.stream.read(self.chunk, exception_on_overflow=True)
|
|
|
|
self.frames.append(data)
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
except KeyboardInterrupt:
|
2023-10-09 20:11:45 +00:00
|
|
|
logger.info("Done recording")
|
2023-02-08 01:59:02 +00:00
|
|
|
except Exception as e:
|
2023-10-09 20:11:45 +00:00
|
|
|
logger.error(str(e))
|
|
|
|
|
|
|
|
def off_hook_condition(self):
|
|
|
|
if self.hook_type == "NC":
|
|
|
|
return not self.hook.is_pressed
|
|
|
|
else: # Assuming default is "NO" if not "NC"
|
|
|
|
return self.hook.is_pressed
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
def play(self, file):
|
2023-10-09 20:11:45 +00:00
|
|
|
self.init_audio()
|
|
|
|
with wave.open(file, "rb") as wf:
|
|
|
|
self.stream = self.audio.open(
|
|
|
|
format=self.audio.get_format_from_width(wf.getsampwidth()),
|
|
|
|
channels=wf.getnchannels(),
|
|
|
|
rate=wf.getframerate(),
|
|
|
|
output=True,
|
|
|
|
)
|
|
|
|
data = wf.readframes(self.chunk)
|
|
|
|
while data:
|
|
|
|
self.stream.write(data)
|
|
|
|
data = wf.readframes(self.chunk)
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
def stop(self):
|
2023-10-09 20:11:45 +00:00
|
|
|
if self.stream:
|
|
|
|
self.stream.stop_stream()
|
|
|
|
self.stream.close()
|
|
|
|
|
|
|
|
if self.audio:
|
|
|
|
self.audio.terminate()
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
def close(self, output_file):
|
2023-10-09 20:11:45 +00:00
|
|
|
try:
|
|
|
|
with wave.open(output_file, "wb") as wavefile:
|
|
|
|
wavefile.setnchannels(self.chans)
|
|
|
|
wavefile.setsampwidth(self.audio.get_sample_size(self.format))
|
|
|
|
wavefile.setframerate(self.samp_rate)
|
|
|
|
wavefile.writeframes(b"".join(self.frames))
|
|
|
|
except OSError as e:
|
|
|
|
logger.error(f"Error writing to file {output_file}. Error: {e}")
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
def postProcess(self, outputFile):
|
|
|
|
source = AudioSegment.from_wav(outputFile + ".wav")
|
2023-10-09 20:11:45 +00:00
|
|
|
filtered = self.filter_audio(source)
|
|
|
|
normalized = self.normalize_audio(filtered)
|
|
|
|
compressed = self.compress_audio(normalized)
|
2023-02-08 01:59:02 +00:00
|
|
|
|
|
|
|
normalized.export(outputFile + "normalized.wav", format="wav")
|
|
|
|
compressed.export(outputFile + "compressed.mp3", format="mp3")
|
|
|
|
|
2023-10-09 20:11:45 +00:00
|
|
|
def filter_audio(self, audio):
|
|
|
|
logger.info("Filtering...")
|
|
|
|
return band_pass_filter(audio, 300, 10000)
|
|
|
|
|
|
|
|
def normalize_audio(self, audio):
|
|
|
|
logger.info("Normalizing...")
|
|
|
|
return normalize(audio)
|
|
|
|
|
|
|
|
def compress_audio(self, audio):
|
|
|
|
logger.info("Compress Dynamic Range")
|
|
|
|
return compress_dynamic_range(audio)
|